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