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