##// END OF EJS Templates
setup: unconditionally enable the `long-paths-support` option on Windows...
Matt Harbison -
r50290:747c4fc2 default
parent child Browse files
Show More
@@ -1,1777 +1,1756 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 user_options = build_ext.user_options + [
670 (
671 'long-paths-support',
672 None,
673 'enable support for long paths on '
674 'Windows (off by default and '
675 'experimental)',
676 ),
677 ]
678 669
679 670 LONG_PATHS_MANIFEST = """
680 671 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
681 672 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
682 673 <application>
683 674 <windowsSettings
684 675 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
685 676 <ws2:longPathAware>true</ws2:longPathAware>
686 677 </windowsSettings>
687 678 </application>
688 679 </assembly>"""
689 680
690 681 def initialize_options(self):
691 682 build_ext.initialize_options(self)
692 self.long_paths_support = False
693 683
694 684 def build_extensions(self):
695 685 if os.name != 'nt':
696 686 return
697 687 if isinstance(self.compiler, HackedMingw32CCompiler):
698 688 self.compiler.compiler_so = self.compiler.compiler # no -mdll
699 689 self.compiler.dll_libraries = [] # no -lmsrvc90
700 690
701 691 pythonlib = None
702 692
703 693 dirname = os.path.dirname(self.get_ext_fullpath('dummy'))
704 694 self.hgtarget = os.path.join(dirname, 'hg')
705 695
706 696 if getattr(sys, 'dllhandle', None):
707 697 # Different Python installs can have different Python library
708 698 # names. e.g. the official CPython distribution uses pythonXY.dll
709 699 # and MinGW uses libpythonX.Y.dll.
710 700 _kernel32 = ctypes.windll.kernel32
711 701 _kernel32.GetModuleFileNameA.argtypes = [
712 702 ctypes.c_void_p,
713 703 ctypes.c_void_p,
714 704 ctypes.c_ulong,
715 705 ]
716 706 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
717 707 size = 1000
718 708 buf = ctypes.create_string_buffer(size + 1)
719 709 filelen = _kernel32.GetModuleFileNameA(
720 710 sys.dllhandle, ctypes.byref(buf), size
721 711 )
722 712
723 713 if filelen > 0 and filelen != size:
724 714 dllbasename = os.path.basename(buf.value)
725 715 if not dllbasename.lower().endswith(b'.dll'):
726 716 raise SystemExit(
727 717 'Python DLL does not end with .dll: %s' % dllbasename
728 718 )
729 719 pythonlib = dllbasename[:-4]
730 720
731 721 # Copy the pythonXY.dll next to the binary so that it runs
732 722 # without tampering with PATH.
733 723 dest = os.path.join(
734 724 os.path.dirname(self.hgtarget),
735 725 os.fsdecode(dllbasename),
736 726 )
737 727
738 728 if not os.path.exists(dest):
739 729 shutil.copy(buf.value, dest)
740 730
741 731 # Also overwrite python3.dll so that hgext.git is usable.
742 732 # TODO: also handle the MSYS flavor
743 733 python_x = os.path.join(
744 734 os.path.dirname(os.fsdecode(buf.value)),
745 735 "python3.dll",
746 736 )
747 737
748 738 if os.path.exists(python_x):
749 739 dest = os.path.join(
750 740 os.path.dirname(self.hgtarget),
751 741 os.path.basename(python_x),
752 742 )
753 743
754 744 shutil.copy(python_x, dest)
755 745
756 746 if not pythonlib:
757 747 log.warn(
758 748 'could not determine Python DLL filename; assuming pythonXY'
759 749 )
760 750
761 751 hv = sys.hexversion
762 752 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
763 753
764 754 log.info('using %s as Python library name' % pythonlib)
765 755 with open('mercurial/hgpythonlib.h', 'wb') as f:
766 756 f.write(b'/* this file is autogenerated by setup.py */\n')
767 757 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
768 758
769 759 objects = self.compiler.compile(
770 760 ['mercurial/exewrapper.c'],
771 761 output_dir=self.build_temp,
772 762 macros=[('_UNICODE', None), ('UNICODE', None)],
773 763 )
774 764 self.compiler.link_executable(
775 765 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
776 766 )
777 if self.long_paths_support:
778 self.addlongpathsmanifest()
767
768 self.addlongpathsmanifest()
779 769
780 770 def addlongpathsmanifest(self):
781 r"""Add manifest pieces so that hg.exe understands long paths
782
783 This is an EXPERIMENTAL feature, use with care.
784 To enable long paths support, one needs to do two things:
785 - build Mercurial with --long-paths-support option
786 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
787 LongPathsEnabled to have value 1.
788
789 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
790 it happens because Mercurial uses mt.exe circa 2008, which is not
791 yet aware of long paths support in the manifest (I think so at least).
792 This does not stop mt.exe from embedding/merging the XML properly.
771 """Add manifest pieces so that hg.exe understands long paths
793 772
794 773 Why resource #1 should be used for .exe manifests? I don't know and
795 774 wasn't able to find an explanation for mortals. But it seems to work.
796 775 """
797 776 exefname = self.compiler.executable_filename(self.hgtarget)
798 777 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
799 778 os.close(fdauto)
800 779 with open(manfname, 'w') as f:
801 780 f.write(self.LONG_PATHS_MANIFEST)
802 781 log.info("long paths manifest is written to '%s'" % manfname)
803 782 inputresource = '-inputresource:%s;#1' % exefname
804 783 outputresource = '-outputresource:%s;#1' % exefname
805 784 log.info("running mt.exe to update hg.exe's manifest in-place")
806 785 # supplying both -manifest and -inputresource to mt.exe makes
807 786 # it merge the embedded and supplied manifests in the -outputresource
808 787 self.spawn(
809 788 [
810 'mt.exe',
789 self.compiler.mt,
811 790 '-nologo',
812 791 '-manifest',
813 792 manfname,
814 793 inputresource,
815 794 outputresource,
816 795 ]
817 796 )
818 797 log.info("done updating hg.exe's manifest")
819 798 os.remove(manfname)
820 799
821 800 @property
822 801 def hgexepath(self):
823 802 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
824 803 return os.path.join(self.build_temp, dir, 'hg.exe')
825 804
826 805
827 806 class hgbuilddoc(Command):
828 807 description = 'build documentation'
829 808 user_options = [
830 809 ('man', None, 'generate man pages'),
831 810 ('html', None, 'generate html pages'),
832 811 ]
833 812
834 813 def initialize_options(self):
835 814 self.man = None
836 815 self.html = None
837 816
838 817 def finalize_options(self):
839 818 # If --man or --html are set, only generate what we're told to.
840 819 # Otherwise generate everything.
841 820 have_subset = self.man is not None or self.html is not None
842 821
843 822 if have_subset:
844 823 self.man = True if self.man else False
845 824 self.html = True if self.html else False
846 825 else:
847 826 self.man = True
848 827 self.html = True
849 828
850 829 def run(self):
851 830 def normalizecrlf(p):
852 831 with open(p, 'rb') as fh:
853 832 orig = fh.read()
854 833
855 834 if b'\r\n' not in orig:
856 835 return
857 836
858 837 log.info('normalizing %s to LF line endings' % p)
859 838 with open(p, 'wb') as fh:
860 839 fh.write(orig.replace(b'\r\n', b'\n'))
861 840
862 841 def gentxt(root):
863 842 txt = 'doc/%s.txt' % root
864 843 log.info('generating %s' % txt)
865 844 res, out, err = runcmd(
866 845 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
867 846 )
868 847 if res:
869 848 raise SystemExit(
870 849 'error running gendoc.py: %s'
871 850 % '\n'.join([sysstr(out), sysstr(err)])
872 851 )
873 852
874 853 with open(txt, 'wb') as fh:
875 854 fh.write(out)
876 855
877 856 def gengendoc(root):
878 857 gendoc = 'doc/%s.gendoc.txt' % root
879 858
880 859 log.info('generating %s' % gendoc)
881 860 res, out, err = runcmd(
882 861 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
883 862 os.environ,
884 863 cwd='doc',
885 864 )
886 865 if res:
887 866 raise SystemExit(
888 867 'error running gendoc: %s'
889 868 % '\n'.join([sysstr(out), sysstr(err)])
890 869 )
891 870
892 871 with open(gendoc, 'wb') as fh:
893 872 fh.write(out)
894 873
895 874 def genman(root):
896 875 log.info('generating doc/%s' % root)
897 876 res, out, err = runcmd(
898 877 [
899 878 sys.executable,
900 879 'runrst',
901 880 'hgmanpage',
902 881 '--halt',
903 882 'warning',
904 883 '--strip-elements-with-class',
905 884 'htmlonly',
906 885 '%s.txt' % root,
907 886 root,
908 887 ],
909 888 os.environ,
910 889 cwd='doc',
911 890 )
912 891 if res:
913 892 raise SystemExit(
914 893 'error running runrst: %s'
915 894 % '\n'.join([sysstr(out), sysstr(err)])
916 895 )
917 896
918 897 normalizecrlf('doc/%s' % root)
919 898
920 899 def genhtml(root):
921 900 log.info('generating doc/%s.html' % root)
922 901 res, out, err = runcmd(
923 902 [
924 903 sys.executable,
925 904 'runrst',
926 905 'html',
927 906 '--halt',
928 907 'warning',
929 908 '--link-stylesheet',
930 909 '--stylesheet-path',
931 910 'style.css',
932 911 '%s.txt' % root,
933 912 '%s.html' % root,
934 913 ],
935 914 os.environ,
936 915 cwd='doc',
937 916 )
938 917 if res:
939 918 raise SystemExit(
940 919 'error running runrst: %s'
941 920 % '\n'.join([sysstr(out), sysstr(err)])
942 921 )
943 922
944 923 normalizecrlf('doc/%s.html' % root)
945 924
946 925 # This logic is duplicated in doc/Makefile.
947 926 sources = {
948 927 f
949 928 for f in os.listdir('mercurial/helptext')
950 929 if re.search(r'[0-9]\.txt$', f)
951 930 }
952 931
953 932 # common.txt is a one-off.
954 933 gentxt('common')
955 934
956 935 for source in sorted(sources):
957 936 assert source[-4:] == '.txt'
958 937 root = source[:-4]
959 938
960 939 gentxt(root)
961 940 gengendoc(root)
962 941
963 942 if self.man:
964 943 genman(root)
965 944 if self.html:
966 945 genhtml(root)
967 946
968 947
969 948 class hginstall(install):
970 949
971 950 user_options = install.user_options + [
972 951 (
973 952 'old-and-unmanageable',
974 953 None,
975 954 'noop, present for eggless setuptools compat',
976 955 ),
977 956 (
978 957 'single-version-externally-managed',
979 958 None,
980 959 'noop, present for eggless setuptools compat',
981 960 ),
982 961 ]
983 962
984 963 sub_commands = install.sub_commands + [
985 964 ('install_completion', lambda self: True)
986 965 ]
987 966
988 967 # Also helps setuptools not be sad while we refuse to create eggs.
989 968 single_version_externally_managed = True
990 969
991 970 def get_sub_commands(self):
992 971 # Screen out egg related commands to prevent egg generation. But allow
993 972 # mercurial.egg-info generation, since that is part of modern
994 973 # packaging.
995 974 excl = {'bdist_egg'}
996 975 return filter(lambda x: x not in excl, install.get_sub_commands(self))
997 976
998 977
999 978 class hginstalllib(install_lib):
1000 979 """
1001 980 This is a specialization of install_lib that replaces the copy_file used
1002 981 there so that it supports setting the mode of files after copying them,
1003 982 instead of just preserving the mode that the files originally had. If your
1004 983 system has a umask of something like 027, preserving the permissions when
1005 984 copying will lead to a broken install.
1006 985
1007 986 Note that just passing keep_permissions=False to copy_file would be
1008 987 insufficient, as it might still be applying a umask.
1009 988 """
1010 989
1011 990 def run(self):
1012 991 realcopyfile = file_util.copy_file
1013 992
1014 993 def copyfileandsetmode(*args, **kwargs):
1015 994 src, dst = args[0], args[1]
1016 995 dst, copied = realcopyfile(*args, **kwargs)
1017 996 if copied:
1018 997 st = os.stat(src)
1019 998 # Persist executable bit (apply it to group and other if user
1020 999 # has it)
1021 1000 if st[stat.ST_MODE] & stat.S_IXUSR:
1022 1001 setmode = int('0755', 8)
1023 1002 else:
1024 1003 setmode = int('0644', 8)
1025 1004 m = stat.S_IMODE(st[stat.ST_MODE])
1026 1005 m = (m & ~int('0777', 8)) | setmode
1027 1006 os.chmod(dst, m)
1028 1007
1029 1008 file_util.copy_file = copyfileandsetmode
1030 1009 try:
1031 1010 install_lib.run(self)
1032 1011 finally:
1033 1012 file_util.copy_file = realcopyfile
1034 1013
1035 1014
1036 1015 class hginstallscripts(install_scripts):
1037 1016 """
1038 1017 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1039 1018 the configured directory for modules. If possible, the path is made relative
1040 1019 to the directory for scripts.
1041 1020 """
1042 1021
1043 1022 def initialize_options(self):
1044 1023 install_scripts.initialize_options(self)
1045 1024
1046 1025 self.install_lib = None
1047 1026
1048 1027 def finalize_options(self):
1049 1028 install_scripts.finalize_options(self)
1050 1029 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1051 1030
1052 1031 def run(self):
1053 1032 install_scripts.run(self)
1054 1033
1055 1034 # It only makes sense to replace @LIBDIR@ with the install path if
1056 1035 # the install path is known. For wheels, the logic below calculates
1057 1036 # the libdir to be "../..". This is because the internal layout of a
1058 1037 # wheel archive looks like:
1059 1038 #
1060 1039 # mercurial-3.6.1.data/scripts/hg
1061 1040 # mercurial/__init__.py
1062 1041 #
1063 1042 # When installing wheels, the subdirectories of the "<pkg>.data"
1064 1043 # directory are translated to system local paths and files therein
1065 1044 # are copied in place. The mercurial/* files are installed into the
1066 1045 # site-packages directory. However, the site-packages directory
1067 1046 # isn't known until wheel install time. This means we have no clue
1068 1047 # at wheel generation time what the installed site-packages directory
1069 1048 # will be. And, wheels don't appear to provide the ability to register
1070 1049 # custom code to run during wheel installation. This all means that
1071 1050 # we can't reliably set the libdir in wheels: the default behavior
1072 1051 # of looking in sys.path must do.
1073 1052
1074 1053 if (
1075 1054 os.path.splitdrive(self.install_dir)[0]
1076 1055 != os.path.splitdrive(self.install_lib)[0]
1077 1056 ):
1078 1057 # can't make relative paths from one drive to another, so use an
1079 1058 # absolute path instead
1080 1059 libdir = self.install_lib
1081 1060 else:
1082 1061 libdir = os.path.relpath(self.install_lib, self.install_dir)
1083 1062
1084 1063 for outfile in self.outfiles:
1085 1064 with open(outfile, 'rb') as fp:
1086 1065 data = fp.read()
1087 1066
1088 1067 # skip binary files
1089 1068 if b'\0' in data:
1090 1069 continue
1091 1070
1092 1071 # During local installs, the shebang will be rewritten to the final
1093 1072 # install path. During wheel packaging, the shebang has a special
1094 1073 # value.
1095 1074 if data.startswith(b'#!python'):
1096 1075 log.info(
1097 1076 'not rewriting @LIBDIR@ in %s because install path '
1098 1077 'not known' % outfile
1099 1078 )
1100 1079 continue
1101 1080
1102 1081 data = data.replace(b'@LIBDIR@', libdir.encode('unicode_escape'))
1103 1082 with open(outfile, 'wb') as fp:
1104 1083 fp.write(data)
1105 1084
1106 1085
1107 1086 class hginstallcompletion(Command):
1108 1087 description = 'Install shell completion'
1109 1088
1110 1089 def initialize_options(self):
1111 1090 self.install_dir = None
1112 1091 self.outputs = []
1113 1092
1114 1093 def finalize_options(self):
1115 1094 self.set_undefined_options(
1116 1095 'install_data', ('install_dir', 'install_dir')
1117 1096 )
1118 1097
1119 1098 def get_outputs(self):
1120 1099 return self.outputs
1121 1100
1122 1101 def run(self):
1123 1102 for src, dir_path, dest in (
1124 1103 (
1125 1104 'bash_completion',
1126 1105 ('share', 'bash-completion', 'completions'),
1127 1106 'hg',
1128 1107 ),
1129 1108 ('zsh_completion', ('share', 'zsh', 'site-functions'), '_hg'),
1130 1109 ):
1131 1110 dir = os.path.join(self.install_dir, *dir_path)
1132 1111 self.mkpath(dir)
1133 1112
1134 1113 dest = os.path.join(dir, dest)
1135 1114 self.outputs.append(dest)
1136 1115 self.copy_file(os.path.join('contrib', src), dest)
1137 1116
1138 1117
1139 1118 # virtualenv installs custom distutils/__init__.py and
1140 1119 # distutils/distutils.cfg files which essentially proxy back to the
1141 1120 # "real" distutils in the main Python install. The presence of this
1142 1121 # directory causes py2exe to pick up the "hacked" distutils package
1143 1122 # from the virtualenv and "import distutils" will fail from the py2exe
1144 1123 # build because the "real" distutils files can't be located.
1145 1124 #
1146 1125 # We work around this by monkeypatching the py2exe code finding Python
1147 1126 # modules to replace the found virtualenv distutils modules with the
1148 1127 # original versions via filesystem scanning. This is a bit hacky. But
1149 1128 # it allows us to use virtualenvs for py2exe packaging, which is more
1150 1129 # deterministic and reproducible.
1151 1130 #
1152 1131 # It's worth noting that the common StackOverflow suggestions for this
1153 1132 # problem involve copying the original distutils files into the
1154 1133 # virtualenv or into the staging directory after setup() is invoked.
1155 1134 # The former is very brittle and can easily break setup(). Our hacking
1156 1135 # of the found modules routine has a similar result as copying the files
1157 1136 # manually. But it makes fewer assumptions about how py2exe works and
1158 1137 # is less brittle.
1159 1138
1160 1139 # This only catches virtualenvs made with virtualenv (as opposed to
1161 1140 # venv, which is likely what Python 3 uses).
1162 1141 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1163 1142
1164 1143 if py2exehacked:
1165 1144 from distutils.command.py2exe import py2exe as buildpy2exe
1166 1145 from py2exe.mf import Module as py2exemodule
1167 1146
1168 1147 class hgbuildpy2exe(buildpy2exe):
1169 1148 def find_needed_modules(self, mf, files, modules):
1170 1149 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1171 1150
1172 1151 # Replace virtualenv's distutils modules with the real ones.
1173 1152 modules = {}
1174 1153 for k, v in res.modules.items():
1175 1154 if k != 'distutils' and not k.startswith('distutils.'):
1176 1155 modules[k] = v
1177 1156
1178 1157 res.modules = modules
1179 1158
1180 1159 import opcode
1181 1160
1182 1161 distutilsreal = os.path.join(
1183 1162 os.path.dirname(opcode.__file__), 'distutils'
1184 1163 )
1185 1164
1186 1165 for root, dirs, files in os.walk(distutilsreal):
1187 1166 for f in sorted(files):
1188 1167 if not f.endswith('.py'):
1189 1168 continue
1190 1169
1191 1170 full = os.path.join(root, f)
1192 1171
1193 1172 parents = ['distutils']
1194 1173
1195 1174 if root != distutilsreal:
1196 1175 rel = os.path.relpath(root, distutilsreal)
1197 1176 parents.extend(p for p in rel.split(os.sep))
1198 1177
1199 1178 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1200 1179
1201 1180 if modname.startswith('distutils.tests.'):
1202 1181 continue
1203 1182
1204 1183 if modname.endswith('.__init__'):
1205 1184 modname = modname[: -len('.__init__')]
1206 1185 path = os.path.dirname(full)
1207 1186 else:
1208 1187 path = None
1209 1188
1210 1189 res.modules[modname] = py2exemodule(
1211 1190 modname, full, path=path
1212 1191 )
1213 1192
1214 1193 if 'distutils' not in res.modules:
1215 1194 raise SystemExit('could not find distutils modules')
1216 1195
1217 1196 return res
1218 1197
1219 1198
1220 1199 cmdclass = {
1221 1200 'build': hgbuild,
1222 1201 'build_doc': hgbuilddoc,
1223 1202 'build_mo': hgbuildmo,
1224 1203 'build_ext': hgbuildext,
1225 1204 'build_py': hgbuildpy,
1226 1205 'build_scripts': hgbuildscripts,
1227 1206 'build_hgextindex': buildhgextindex,
1228 1207 'install': hginstall,
1229 1208 'install_completion': hginstallcompletion,
1230 1209 'install_lib': hginstalllib,
1231 1210 'install_scripts': hginstallscripts,
1232 1211 'build_hgexe': buildhgexe,
1233 1212 }
1234 1213
1235 1214 if py2exehacked:
1236 1215 cmdclass['py2exe'] = hgbuildpy2exe
1237 1216
1238 1217 packages = [
1239 1218 'mercurial',
1240 1219 'mercurial.cext',
1241 1220 'mercurial.cffi',
1242 1221 'mercurial.defaultrc',
1243 1222 'mercurial.dirstateutils',
1244 1223 'mercurial.helptext',
1245 1224 'mercurial.helptext.internals',
1246 1225 'mercurial.hgweb',
1247 1226 'mercurial.interfaces',
1248 1227 'mercurial.pure',
1249 1228 'mercurial.templates',
1250 1229 'mercurial.thirdparty',
1251 1230 'mercurial.thirdparty.attr',
1252 1231 'mercurial.thirdparty.zope',
1253 1232 'mercurial.thirdparty.zope.interface',
1254 1233 'mercurial.upgrade_utils',
1255 1234 'mercurial.utils',
1256 1235 'mercurial.revlogutils',
1257 1236 'mercurial.testing',
1258 1237 'hgext',
1259 1238 'hgext.convert',
1260 1239 'hgext.fsmonitor',
1261 1240 'hgext.fastannotate',
1262 1241 'hgext.fsmonitor.pywatchman',
1263 1242 'hgext.git',
1264 1243 'hgext.highlight',
1265 1244 'hgext.hooklib',
1266 1245 'hgext.infinitepush',
1267 1246 'hgext.largefiles',
1268 1247 'hgext.lfs',
1269 1248 'hgext.narrow',
1270 1249 'hgext.remotefilelog',
1271 1250 'hgext.zeroconf',
1272 1251 'hgext3rd',
1273 1252 'hgdemandimport',
1274 1253 ]
1275 1254
1276 1255 for name in os.listdir(os.path.join('mercurial', 'templates')):
1277 1256 if name != '__pycache__' and os.path.isdir(
1278 1257 os.path.join('mercurial', 'templates', name)
1279 1258 ):
1280 1259 packages.append('mercurial.templates.%s' % name)
1281 1260
1282 1261 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1283 1262 # py2exe can't cope with namespace packages very well, so we have to
1284 1263 # install any hgext3rd.* extensions that we want in the final py2exe
1285 1264 # image here. This is gross, but you gotta do what you gotta do.
1286 1265 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1287 1266
1288 1267 common_depends = [
1289 1268 'mercurial/bitmanipulation.h',
1290 1269 'mercurial/compat.h',
1291 1270 'mercurial/cext/util.h',
1292 1271 ]
1293 1272 common_include_dirs = ['mercurial']
1294 1273
1295 1274 common_cflags = []
1296 1275
1297 1276 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1298 1277 # makes declarations not at the top of a scope in the headers.
1299 1278 if os.name != 'nt' and sys.version_info[1] < 9:
1300 1279 common_cflags = ['-Werror=declaration-after-statement']
1301 1280
1302 1281 osutil_cflags = []
1303 1282 osutil_ldflags = []
1304 1283
1305 1284 # platform specific macros
1306 1285 for plat, func in [('bsd', 'setproctitle')]:
1307 1286 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1308 1287 osutil_cflags.append('-DHAVE_%s' % func.upper())
1309 1288
1310 1289 for plat, macro, code in [
1311 1290 (
1312 1291 'bsd|darwin',
1313 1292 'BSD_STATFS',
1314 1293 '''
1315 1294 #include <sys/param.h>
1316 1295 #include <sys/mount.h>
1317 1296 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1318 1297 ''',
1319 1298 ),
1320 1299 (
1321 1300 'linux',
1322 1301 'LINUX_STATFS',
1323 1302 '''
1324 1303 #include <linux/magic.h>
1325 1304 #include <sys/vfs.h>
1326 1305 int main() { struct statfs s; return sizeof(s.f_type); }
1327 1306 ''',
1328 1307 ),
1329 1308 ]:
1330 1309 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1331 1310 osutil_cflags.append('-DHAVE_%s' % macro)
1332 1311
1333 1312 if sys.platform == 'darwin':
1334 1313 osutil_ldflags += ['-framework', 'ApplicationServices']
1335 1314
1336 1315 if sys.platform == 'sunos5':
1337 1316 osutil_ldflags += ['-lsocket']
1338 1317
1339 1318 xdiff_srcs = [
1340 1319 'mercurial/thirdparty/xdiff/xdiffi.c',
1341 1320 'mercurial/thirdparty/xdiff/xprepare.c',
1342 1321 'mercurial/thirdparty/xdiff/xutils.c',
1343 1322 ]
1344 1323
1345 1324 xdiff_headers = [
1346 1325 'mercurial/thirdparty/xdiff/xdiff.h',
1347 1326 'mercurial/thirdparty/xdiff/xdiffi.h',
1348 1327 'mercurial/thirdparty/xdiff/xinclude.h',
1349 1328 'mercurial/thirdparty/xdiff/xmacros.h',
1350 1329 'mercurial/thirdparty/xdiff/xprepare.h',
1351 1330 'mercurial/thirdparty/xdiff/xtypes.h',
1352 1331 'mercurial/thirdparty/xdiff/xutils.h',
1353 1332 ]
1354 1333
1355 1334
1356 1335 class RustCompilationError(CCompilerError):
1357 1336 """Exception class for Rust compilation errors."""
1358 1337
1359 1338
1360 1339 class RustExtension(Extension):
1361 1340 """Base classes for concrete Rust Extension classes."""
1362 1341
1363 1342 rusttargetdir = os.path.join('rust', 'target', 'release')
1364 1343
1365 1344 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1366 1345 Extension.__init__(self, mpath, sources, **kw)
1367 1346 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1368 1347
1369 1348 # adding Rust source and control files to depends so that the extension
1370 1349 # gets rebuilt if they've changed
1371 1350 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1372 1351 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1373 1352 if os.path.exists(cargo_lock):
1374 1353 self.depends.append(cargo_lock)
1375 1354 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1376 1355 self.depends.extend(
1377 1356 os.path.join(dirpath, fname)
1378 1357 for fname in fnames
1379 1358 if os.path.splitext(fname)[1] == '.rs'
1380 1359 )
1381 1360
1382 1361 @staticmethod
1383 1362 def rustdylibsuffix():
1384 1363 """Return the suffix for shared libraries produced by rustc.
1385 1364
1386 1365 See also: https://doc.rust-lang.org/reference/linkage.html
1387 1366 """
1388 1367 if sys.platform == 'darwin':
1389 1368 return '.dylib'
1390 1369 elif os.name == 'nt':
1391 1370 return '.dll'
1392 1371 else:
1393 1372 return '.so'
1394 1373
1395 1374 def rustbuild(self):
1396 1375 env = os.environ.copy()
1397 1376 if 'HGTEST_RESTOREENV' in env:
1398 1377 # Mercurial tests change HOME to a temporary directory,
1399 1378 # but, if installed with rustup, the Rust toolchain needs
1400 1379 # HOME to be correct (otherwise the 'no default toolchain'
1401 1380 # error message is issued and the build fails).
1402 1381 # This happens currently with test-hghave.t, which does
1403 1382 # invoke this build.
1404 1383
1405 1384 # Unix only fix (os.path.expanduser not really reliable if
1406 1385 # HOME is shadowed like this)
1407 1386 import pwd
1408 1387
1409 1388 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1410 1389
1411 1390 cargocmd = ['cargo', 'rustc', '--release']
1412 1391
1413 1392 rust_features = env.get("HG_RUST_FEATURES")
1414 1393 if rust_features:
1415 1394 cargocmd.extend(('--features', rust_features))
1416 1395
1417 1396 cargocmd.append('--')
1418 1397 if sys.platform == 'darwin':
1419 1398 cargocmd.extend(
1420 1399 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1421 1400 )
1422 1401 try:
1423 1402 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1424 1403 except FileNotFoundError:
1425 1404 raise RustCompilationError("Cargo not found")
1426 1405 except PermissionError:
1427 1406 raise RustCompilationError(
1428 1407 "Cargo found, but permission to execute it is denied"
1429 1408 )
1430 1409 except subprocess.CalledProcessError:
1431 1410 raise RustCompilationError(
1432 1411 "Cargo failed. Working directory: %r, "
1433 1412 "command: %r, environment: %r"
1434 1413 % (self.rustsrcdir, cargocmd, env)
1435 1414 )
1436 1415
1437 1416
1438 1417 class RustStandaloneExtension(RustExtension):
1439 1418 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1440 1419 RustExtension.__init__(
1441 1420 self, pydottedname, [], dylibname, rustcrate, **kw
1442 1421 )
1443 1422 self.dylibname = dylibname
1444 1423
1445 1424 def build(self, target_dir):
1446 1425 self.rustbuild()
1447 1426 target = [target_dir]
1448 1427 target.extend(self.name.split('.'))
1449 1428 target[-1] += DYLIB_SUFFIX
1450 1429 shutil.copy2(
1451 1430 os.path.join(
1452 1431 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1453 1432 ),
1454 1433 os.path.join(*target),
1455 1434 )
1456 1435
1457 1436
1458 1437 extmodules = [
1459 1438 Extension(
1460 1439 'mercurial.cext.base85',
1461 1440 ['mercurial/cext/base85.c'],
1462 1441 include_dirs=common_include_dirs,
1463 1442 extra_compile_args=common_cflags,
1464 1443 depends=common_depends,
1465 1444 ),
1466 1445 Extension(
1467 1446 'mercurial.cext.bdiff',
1468 1447 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1469 1448 include_dirs=common_include_dirs,
1470 1449 extra_compile_args=common_cflags,
1471 1450 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1472 1451 ),
1473 1452 Extension(
1474 1453 'mercurial.cext.mpatch',
1475 1454 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1476 1455 include_dirs=common_include_dirs,
1477 1456 extra_compile_args=common_cflags,
1478 1457 depends=common_depends,
1479 1458 ),
1480 1459 Extension(
1481 1460 'mercurial.cext.parsers',
1482 1461 [
1483 1462 'mercurial/cext/charencode.c',
1484 1463 'mercurial/cext/dirs.c',
1485 1464 'mercurial/cext/manifest.c',
1486 1465 'mercurial/cext/parsers.c',
1487 1466 'mercurial/cext/pathencode.c',
1488 1467 'mercurial/cext/revlog.c',
1489 1468 ],
1490 1469 include_dirs=common_include_dirs,
1491 1470 extra_compile_args=common_cflags,
1492 1471 depends=common_depends
1493 1472 + [
1494 1473 'mercurial/cext/charencode.h',
1495 1474 'mercurial/cext/revlog.h',
1496 1475 ],
1497 1476 ),
1498 1477 Extension(
1499 1478 'mercurial.cext.osutil',
1500 1479 ['mercurial/cext/osutil.c'],
1501 1480 include_dirs=common_include_dirs,
1502 1481 extra_compile_args=common_cflags + osutil_cflags,
1503 1482 extra_link_args=osutil_ldflags,
1504 1483 depends=common_depends,
1505 1484 ),
1506 1485 Extension(
1507 1486 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1508 1487 [
1509 1488 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1510 1489 ],
1511 1490 extra_compile_args=common_cflags,
1512 1491 ),
1513 1492 Extension(
1514 1493 'mercurial.thirdparty.sha1dc',
1515 1494 [
1516 1495 'mercurial/thirdparty/sha1dc/cext.c',
1517 1496 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1518 1497 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1519 1498 ],
1520 1499 extra_compile_args=common_cflags,
1521 1500 ),
1522 1501 Extension(
1523 1502 'hgext.fsmonitor.pywatchman.bser',
1524 1503 ['hgext/fsmonitor/pywatchman/bser.c'],
1525 1504 extra_compile_args=common_cflags,
1526 1505 ),
1527 1506 RustStandaloneExtension(
1528 1507 'mercurial.rustext',
1529 1508 'hg-cpython',
1530 1509 'librusthg',
1531 1510 ),
1532 1511 ]
1533 1512
1534 1513
1535 1514 sys.path.insert(0, 'contrib/python-zstandard')
1536 1515 import setup_zstd
1537 1516
1538 1517 zstd = setup_zstd.get_c_extension(
1539 1518 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1540 1519 )
1541 1520 zstd.extra_compile_args += common_cflags
1542 1521 extmodules.append(zstd)
1543 1522
1544 1523 try:
1545 1524 from distutils import cygwinccompiler
1546 1525
1547 1526 # the -mno-cygwin option has been deprecated for years
1548 1527 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1549 1528
1550 1529 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1551 1530 def __init__(self, *args, **kwargs):
1552 1531 mingw32compilerclass.__init__(self, *args, **kwargs)
1553 1532 for i in 'compiler compiler_so linker_exe linker_so'.split():
1554 1533 try:
1555 1534 getattr(self, i).remove('-mno-cygwin')
1556 1535 except ValueError:
1557 1536 pass
1558 1537
1559 1538 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1560 1539 except ImportError:
1561 1540 # the cygwinccompiler package is not available on some Python
1562 1541 # distributions like the ones from the optware project for Synology
1563 1542 # DiskStation boxes
1564 1543 class HackedMingw32CCompiler:
1565 1544 pass
1566 1545
1567 1546
1568 1547 if os.name == 'nt':
1569 1548 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1570 1549 # extra_link_args to distutils.extensions.Extension() doesn't have any
1571 1550 # effect.
1572 1551 from distutils import msvccompiler
1573 1552
1574 1553 msvccompilerclass = msvccompiler.MSVCCompiler
1575 1554
1576 1555 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1577 1556 def initialize(self):
1578 1557 msvccompilerclass.initialize(self)
1579 1558 # "warning LNK4197: export 'func' specified multiple times"
1580 1559 self.ldflags_shared.append('/ignore:4197')
1581 1560 self.ldflags_shared_debug.append('/ignore:4197')
1582 1561
1583 1562 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1584 1563
1585 1564 packagedata = {
1586 1565 'mercurial': [
1587 1566 'locale/*/LC_MESSAGES/hg.mo',
1588 1567 'dummycert.pem',
1589 1568 ],
1590 1569 'mercurial.defaultrc': [
1591 1570 '*.rc',
1592 1571 ],
1593 1572 'mercurial.helptext': [
1594 1573 '*.txt',
1595 1574 ],
1596 1575 'mercurial.helptext.internals': [
1597 1576 '*.txt',
1598 1577 ],
1599 1578 }
1600 1579
1601 1580
1602 1581 def ordinarypath(p):
1603 1582 return p and p[0] != '.' and p[-1] != '~'
1604 1583
1605 1584
1606 1585 for root in ('templates',):
1607 1586 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1608 1587 packagename = curdir.replace(os.sep, '.')
1609 1588 packagedata[packagename] = list(filter(ordinarypath, files))
1610 1589
1611 1590 datafiles = []
1612 1591
1613 1592 # distutils expects version to be str/unicode. Converting it to
1614 1593 # unicode on Python 2 still works because it won't contain any
1615 1594 # non-ascii bytes and will be implicitly converted back to bytes
1616 1595 # when operated on.
1617 1596 assert isinstance(version, str)
1618 1597 setupversion = version
1619 1598
1620 1599 extra = {}
1621 1600
1622 1601 py2exepackages = [
1623 1602 'hgdemandimport',
1624 1603 'hgext3rd',
1625 1604 'hgext',
1626 1605 'email',
1627 1606 # implicitly imported per module policy
1628 1607 # (cffi wouldn't be used as a frozen exe)
1629 1608 'mercurial.cext',
1630 1609 #'mercurial.cffi',
1631 1610 'mercurial.pure',
1632 1611 ]
1633 1612
1634 1613 py2exe_includes = []
1635 1614
1636 1615 py2exeexcludes = []
1637 1616 py2exedllexcludes = ['crypt32.dll']
1638 1617
1639 1618 if issetuptools:
1640 1619 extra['python_requires'] = supportedpy
1641 1620
1642 1621 if py2exeloaded:
1643 1622 extra['console'] = [
1644 1623 {
1645 1624 'script': 'hg',
1646 1625 'copyright': 'Copyright (C) 2005-2022 Olivia Mackall and others',
1647 1626 'product_version': version,
1648 1627 }
1649 1628 ]
1650 1629 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1651 1630 # Need to override hgbuild because it has a private copy of
1652 1631 # build.sub_commands.
1653 1632 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1654 1633 # put dlls in sub directory so that they won't pollute PATH
1655 1634 extra['zipfile'] = 'lib/library.zip'
1656 1635
1657 1636 # We allow some configuration to be supplemented via environment
1658 1637 # variables. This is better than setup.cfg files because it allows
1659 1638 # supplementing configs instead of replacing them.
1660 1639 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1661 1640 if extrapackages:
1662 1641 py2exepackages.extend(extrapackages.split(' '))
1663 1642
1664 1643 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1665 1644 if extra_includes:
1666 1645 py2exe_includes.extend(extra_includes.split(' '))
1667 1646
1668 1647 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1669 1648 if excludes:
1670 1649 py2exeexcludes.extend(excludes.split(' '))
1671 1650
1672 1651 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1673 1652 if dllexcludes:
1674 1653 py2exedllexcludes.extend(dllexcludes.split(' '))
1675 1654
1676 1655 if os.environ.get('PYOXIDIZER'):
1677 1656 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1678 1657
1679 1658 if os.name == 'nt':
1680 1659 # Windows binary file versions for exe/dll files must have the
1681 1660 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1682 1661 setupversion = setupversion.split(r'+', 1)[0]
1683 1662
1684 1663 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1685 1664 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1686 1665 if version:
1687 1666 version = version[0].decode('utf-8')
1688 1667 xcode4 = version.startswith('Xcode') and StrictVersion(
1689 1668 version.split()[1]
1690 1669 ) >= StrictVersion('4.0')
1691 1670 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1692 1671 else:
1693 1672 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1694 1673 # installed, but instead with only command-line tools. Assume
1695 1674 # that only happens on >= Lion, thus no PPC support.
1696 1675 xcode4 = True
1697 1676 xcode51 = False
1698 1677
1699 1678 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1700 1679 # distutils.sysconfig
1701 1680 if xcode4:
1702 1681 os.environ['ARCHFLAGS'] = ''
1703 1682
1704 1683 # XCode 5.1 changes clang such that it now fails to compile if the
1705 1684 # -mno-fused-madd flag is passed, but the version of Python shipped with
1706 1685 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1707 1686 # C extension modules, and a bug has been filed upstream at
1708 1687 # http://bugs.python.org/issue21244. We also need to patch this here
1709 1688 # so Mercurial can continue to compile in the meantime.
1710 1689 if xcode51:
1711 1690 cflags = get_config_var('CFLAGS')
1712 1691 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1713 1692 os.environ['CFLAGS'] = (
1714 1693 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1715 1694 )
1716 1695
1717 1696 setup(
1718 1697 name='mercurial',
1719 1698 version=setupversion,
1720 1699 author='Olivia Mackall and many others',
1721 1700 author_email='mercurial@mercurial-scm.org',
1722 1701 url='https://mercurial-scm.org/',
1723 1702 download_url='https://mercurial-scm.org/release/',
1724 1703 description=(
1725 1704 'Fast scalable distributed SCM (revision control, version '
1726 1705 'control) system'
1727 1706 ),
1728 1707 long_description=(
1729 1708 'Mercurial is a distributed SCM tool written in Python.'
1730 1709 ' It is used by a number of large projects that require'
1731 1710 ' fast, reliable distributed revision control, such as '
1732 1711 'Mozilla.'
1733 1712 ),
1734 1713 license='GNU GPLv2 or any later version',
1735 1714 classifiers=[
1736 1715 'Development Status :: 6 - Mature',
1737 1716 'Environment :: Console',
1738 1717 'Intended Audience :: Developers',
1739 1718 'Intended Audience :: System Administrators',
1740 1719 'License :: OSI Approved :: GNU General Public License (GPL)',
1741 1720 'Natural Language :: Danish',
1742 1721 'Natural Language :: English',
1743 1722 'Natural Language :: German',
1744 1723 'Natural Language :: Italian',
1745 1724 'Natural Language :: Japanese',
1746 1725 'Natural Language :: Portuguese (Brazilian)',
1747 1726 'Operating System :: Microsoft :: Windows',
1748 1727 'Operating System :: OS Independent',
1749 1728 'Operating System :: POSIX',
1750 1729 'Programming Language :: C',
1751 1730 'Programming Language :: Python',
1752 1731 'Topic :: Software Development :: Version Control',
1753 1732 ],
1754 1733 scripts=scripts,
1755 1734 packages=packages,
1756 1735 ext_modules=extmodules,
1757 1736 data_files=datafiles,
1758 1737 package_data=packagedata,
1759 1738 cmdclass=cmdclass,
1760 1739 distclass=hgdist,
1761 1740 options={
1762 1741 'py2exe': {
1763 1742 'bundle_files': 3,
1764 1743 'dll_excludes': py2exedllexcludes,
1765 1744 'includes': py2exe_includes,
1766 1745 'excludes': py2exeexcludes,
1767 1746 'packages': py2exepackages,
1768 1747 },
1769 1748 'bdist_mpkg': {
1770 1749 'zipdist': False,
1771 1750 'license': 'COPYING',
1772 1751 'readme': 'contrib/packaging/macosx/Readme.html',
1773 1752 'welcome': 'contrib/packaging/macosx/Welcome.html',
1774 1753 },
1775 1754 },
1776 1755 **extra
1777 1756 )
General Comments 0
You need to be logged in to leave comments. Login now