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