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