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