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