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