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