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