##// END OF EJS Templates
build: fake PEP440 versions...
Joerg Sonnenberger -
r47262:866eb4d6 default
parent child Browse files
Show More
@@ -1,1848 +1,1836 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 version = '%s+%s-%s' % (ltag, changessince, hgid)
422 version = '%s+hg%s.%s' % (ltag, changessince, hgid)
423 423 if version.endswith('+'):
424 version += time.strftime('%Y%m%d')
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 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
433 version = (
434 '%(latesttag)s+.%(changessincelatesttag)s.%(node).12s' % kw
435 )
434 436 else:
435 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
437 version = '%(latesttag)s+.%(latesttagdistance)s.%(node).12s' % kw
436 438 else:
437 version = kw.get('node', '')[:12]
439 version = '0+' + kw.get('node', '')[:12]
438 440
439 441 if version:
440 442 versionb = version
441 443 if not isinstance(versionb, bytes):
442 444 versionb = versionb.encode('ascii')
443 445
444 446 write_if_changed(
445 447 'mercurial/__version__.py',
446 448 b''.join(
447 449 [
448 450 b'# this file is autogenerated by setup.py\n'
449 451 b'version = b"%s"\n' % versionb,
450 452 ]
451 453 ),
452 454 )
453 455
454 try:
455 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
456 os.environ['HGMODULEPOLICY'] = 'py'
457 from mercurial import __version__
458
459 version = __version__.version
460 except ImportError:
461 version = b'unknown'
462 finally:
463 if oldpolicy is None:
464 del os.environ['HGMODULEPOLICY']
465 else:
466 os.environ['HGMODULEPOLICY'] = oldpolicy
467
468 456
469 457 class hgbuild(build):
470 458 # Insert hgbuildmo first so that files in mercurial/locale/ are found
471 459 # when build_py is run next.
472 460 sub_commands = [('build_mo', None)] + build.sub_commands
473 461
474 462
475 463 class hgbuildmo(build):
476 464
477 465 description = "build translations (.mo files)"
478 466
479 467 def run(self):
480 468 if not find_executable('msgfmt'):
481 469 self.warn(
482 470 "could not find msgfmt executable, no translations "
483 471 "will be built"
484 472 )
485 473 return
486 474
487 475 podir = 'i18n'
488 476 if not os.path.isdir(podir):
489 477 self.warn("could not find %s/ directory" % podir)
490 478 return
491 479
492 480 join = os.path.join
493 481 for po in os.listdir(podir):
494 482 if not po.endswith('.po'):
495 483 continue
496 484 pofile = join(podir, po)
497 485 modir = join('locale', po[:-3], 'LC_MESSAGES')
498 486 mofile = join(modir, 'hg.mo')
499 487 mobuildfile = join('mercurial', mofile)
500 488 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
501 489 if sys.platform != 'sunos5':
502 490 # msgfmt on Solaris does not know about -c
503 491 cmd.append('-c')
504 492 self.mkpath(join('mercurial', modir))
505 493 self.make_file([pofile], mobuildfile, spawn, (cmd,))
506 494
507 495
508 496 class hgdist(Distribution):
509 497 pure = False
510 498 rust = False
511 499 no_rust = False
512 500 cffi = ispypy
513 501
514 502 global_options = Distribution.global_options + [
515 503 ('pure', None, "use pure (slow) Python code instead of C extensions"),
516 504 ('rust', None, "use Rust extensions additionally to C extensions"),
517 505 (
518 506 'no-rust',
519 507 None,
520 508 "do not use Rust extensions additionally to C extensions",
521 509 ),
522 510 ]
523 511
524 512 negative_opt = Distribution.negative_opt.copy()
525 513 boolean_options = ['pure', 'rust', 'no-rust']
526 514 negative_opt['no-rust'] = 'rust'
527 515
528 516 def _set_command_options(self, command_obj, option_dict=None):
529 517 # Not all distutils versions in the wild have boolean_options.
530 518 # This should be cleaned up when we're Python 3 only.
531 519 command_obj.boolean_options = (
532 520 getattr(command_obj, 'boolean_options', []) + self.boolean_options
533 521 )
534 522 return Distribution._set_command_options(
535 523 self, command_obj, option_dict=option_dict
536 524 )
537 525
538 526 def parse_command_line(self):
539 527 ret = Distribution.parse_command_line(self)
540 528 if not (self.rust or self.no_rust):
541 529 hgrustext = os.environ.get('HGWITHRUSTEXT')
542 530 # TODO record it for proper rebuild upon changes
543 531 # (see mercurial/__modulepolicy__.py)
544 532 if hgrustext != 'cpython' and hgrustext is not None:
545 533 if hgrustext:
546 534 msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext
547 535 printf(msg, file=sys.stderr)
548 536 hgrustext = None
549 537 self.rust = hgrustext is not None
550 538 self.no_rust = not self.rust
551 539 return ret
552 540
553 541 def has_ext_modules(self):
554 542 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
555 543 # too late for some cases
556 544 return not self.pure and Distribution.has_ext_modules(self)
557 545
558 546
559 547 # This is ugly as a one-liner. So use a variable.
560 548 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
561 549 buildextnegops['no-zstd'] = 'zstd'
562 550 buildextnegops['no-rust'] = 'rust'
563 551
564 552
565 553 class hgbuildext(build_ext):
566 554 user_options = build_ext.user_options + [
567 555 ('zstd', None, 'compile zstd bindings [default]'),
568 556 ('no-zstd', None, 'do not compile zstd bindings'),
569 557 (
570 558 'rust',
571 559 None,
572 560 'compile Rust extensions if they are in use '
573 561 '(requires Cargo) [default]',
574 562 ),
575 563 ('no-rust', None, 'do not compile Rust extensions'),
576 564 ]
577 565
578 566 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
579 567 negative_opt = buildextnegops
580 568
581 569 def initialize_options(self):
582 570 self.zstd = True
583 571 self.rust = True
584 572
585 573 return build_ext.initialize_options(self)
586 574
587 575 def finalize_options(self):
588 576 # Unless overridden by the end user, build extensions in parallel.
589 577 # Only influences behavior on Python 3.5+.
590 578 if getattr(self, 'parallel', None) is None:
591 579 self.parallel = True
592 580
593 581 return build_ext.finalize_options(self)
594 582
595 583 def build_extensions(self):
596 584 ruststandalones = [
597 585 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
598 586 ]
599 587 self.extensions = [
600 588 e for e in self.extensions if e not in ruststandalones
601 589 ]
602 590 # Filter out zstd if disabled via argument.
603 591 if not self.zstd:
604 592 self.extensions = [
605 593 e for e in self.extensions if e.name != 'mercurial.zstd'
606 594 ]
607 595
608 596 # Build Rust standalon extensions if it'll be used
609 597 # and its build is not explictely disabled (for external build
610 598 # as Linux distributions would do)
611 599 if self.distribution.rust and self.rust:
612 600 if not sys.platform.startswith('linux'):
613 601 self.warn(
614 602 "rust extensions have only been tested on Linux "
615 603 "and may not behave correctly on other platforms"
616 604 )
617 605
618 606 for rustext in ruststandalones:
619 607 rustext.build('' if self.inplace else self.build_lib)
620 608
621 609 return build_ext.build_extensions(self)
622 610
623 611 def build_extension(self, ext):
624 612 if (
625 613 self.distribution.rust
626 614 and self.rust
627 615 and isinstance(ext, RustExtension)
628 616 ):
629 617 ext.rustbuild()
630 618 try:
631 619 build_ext.build_extension(self, ext)
632 620 except CCompilerError:
633 621 if not getattr(ext, 'optional', False):
634 622 raise
635 623 log.warn(
636 624 "Failed to build optional extension '%s' (skipping)", ext.name
637 625 )
638 626
639 627
640 628 class hgbuildscripts(build_scripts):
641 629 def run(self):
642 630 if os.name != 'nt' or self.distribution.pure:
643 631 return build_scripts.run(self)
644 632
645 633 exebuilt = False
646 634 try:
647 635 self.run_command('build_hgexe')
648 636 exebuilt = True
649 637 except (DistutilsError, CCompilerError):
650 638 log.warn('failed to build optional hg.exe')
651 639
652 640 if exebuilt:
653 641 # Copying hg.exe to the scripts build directory ensures it is
654 642 # installed by the install_scripts command.
655 643 hgexecommand = self.get_finalized_command('build_hgexe')
656 644 dest = os.path.join(self.build_dir, 'hg.exe')
657 645 self.mkpath(self.build_dir)
658 646 self.copy_file(hgexecommand.hgexepath, dest)
659 647
660 648 # Remove hg.bat because it is redundant with hg.exe.
661 649 self.scripts.remove('contrib/win32/hg.bat')
662 650
663 651 return build_scripts.run(self)
664 652
665 653
666 654 class hgbuildpy(build_py):
667 655 def finalize_options(self):
668 656 build_py.finalize_options(self)
669 657
670 658 if self.distribution.pure:
671 659 self.distribution.ext_modules = []
672 660 elif self.distribution.cffi:
673 661 from mercurial.cffi import (
674 662 bdiffbuild,
675 663 mpatchbuild,
676 664 )
677 665
678 666 exts = [
679 667 mpatchbuild.ffi.distutils_extension(),
680 668 bdiffbuild.ffi.distutils_extension(),
681 669 ]
682 670 # cffi modules go here
683 671 if sys.platform == 'darwin':
684 672 from mercurial.cffi import osutilbuild
685 673
686 674 exts.append(osutilbuild.ffi.distutils_extension())
687 675 self.distribution.ext_modules = exts
688 676 else:
689 677 h = os.path.join(get_python_inc(), 'Python.h')
690 678 if not os.path.exists(h):
691 679 raise SystemExit(
692 680 'Python headers are required to build '
693 681 'Mercurial but weren\'t found in %s' % h
694 682 )
695 683
696 684 def run(self):
697 685 basepath = os.path.join(self.build_lib, 'mercurial')
698 686 self.mkpath(basepath)
699 687
700 688 rust = self.distribution.rust
701 689 if self.distribution.pure:
702 690 modulepolicy = 'py'
703 691 elif self.build_lib == '.':
704 692 # in-place build should run without rebuilding and Rust extensions
705 693 modulepolicy = 'rust+c-allow' if rust else 'allow'
706 694 else:
707 695 modulepolicy = 'rust+c' if rust else 'c'
708 696
709 697 content = b''.join(
710 698 [
711 699 b'# this file is autogenerated by setup.py\n',
712 700 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
713 701 ]
714 702 )
715 703 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
716 704
717 705 build_py.run(self)
718 706
719 707
720 708 class buildhgextindex(Command):
721 709 description = 'generate prebuilt index of hgext (for frozen package)'
722 710 user_options = []
723 711 _indexfilename = 'hgext/__index__.py'
724 712
725 713 def initialize_options(self):
726 714 pass
727 715
728 716 def finalize_options(self):
729 717 pass
730 718
731 719 def run(self):
732 720 if os.path.exists(self._indexfilename):
733 721 with open(self._indexfilename, 'w') as f:
734 722 f.write('# empty\n')
735 723
736 724 # here no extension enabled, disabled() lists up everything
737 725 code = (
738 726 'import pprint; from mercurial import extensions; '
739 727 'ext = extensions.disabled();'
740 728 'ext.pop("__index__", None);'
741 729 'pprint.pprint(ext)'
742 730 )
743 731 returncode, out, err = runcmd(
744 732 [sys.executable, '-c', code], localhgenv()
745 733 )
746 734 if err or returncode != 0:
747 735 raise DistutilsExecError(err)
748 736
749 737 with open(self._indexfilename, 'wb') as f:
750 738 f.write(b'# this file is autogenerated by setup.py\n')
751 739 f.write(b'docs = ')
752 740 f.write(out)
753 741
754 742
755 743 class buildhgexe(build_ext):
756 744 description = 'compile hg.exe from mercurial/exewrapper.c'
757 745 user_options = build_ext.user_options + [
758 746 (
759 747 'long-paths-support',
760 748 None,
761 749 'enable support for long paths on '
762 750 'Windows (off by default and '
763 751 'experimental)',
764 752 ),
765 753 ]
766 754
767 755 LONG_PATHS_MANIFEST = """
768 756 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
769 757 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
770 758 <application>
771 759 <windowsSettings
772 760 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
773 761 <ws2:longPathAware>true</ws2:longPathAware>
774 762 </windowsSettings>
775 763 </application>
776 764 </assembly>"""
777 765
778 766 def initialize_options(self):
779 767 build_ext.initialize_options(self)
780 768 self.long_paths_support = False
781 769
782 770 def build_extensions(self):
783 771 if os.name != 'nt':
784 772 return
785 773 if isinstance(self.compiler, HackedMingw32CCompiler):
786 774 self.compiler.compiler_so = self.compiler.compiler # no -mdll
787 775 self.compiler.dll_libraries = [] # no -lmsrvc90
788 776
789 777 pythonlib = None
790 778
791 779 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
792 780 self.hgtarget = os.path.join(dir, 'hg')
793 781
794 782 if getattr(sys, 'dllhandle', None):
795 783 # Different Python installs can have different Python library
796 784 # names. e.g. the official CPython distribution uses pythonXY.dll
797 785 # and MinGW uses libpythonX.Y.dll.
798 786 _kernel32 = ctypes.windll.kernel32
799 787 _kernel32.GetModuleFileNameA.argtypes = [
800 788 ctypes.c_void_p,
801 789 ctypes.c_void_p,
802 790 ctypes.c_ulong,
803 791 ]
804 792 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
805 793 size = 1000
806 794 buf = ctypes.create_string_buffer(size + 1)
807 795 filelen = _kernel32.GetModuleFileNameA(
808 796 sys.dllhandle, ctypes.byref(buf), size
809 797 )
810 798
811 799 if filelen > 0 and filelen != size:
812 800 dllbasename = os.path.basename(buf.value)
813 801 if not dllbasename.lower().endswith(b'.dll'):
814 802 raise SystemExit(
815 803 'Python DLL does not end with .dll: %s' % dllbasename
816 804 )
817 805 pythonlib = dllbasename[:-4]
818 806
819 807 # Copy the pythonXY.dll next to the binary so that it runs
820 808 # without tampering with PATH.
821 809 fsdecode = lambda x: x
822 810 if sys.version_info[0] >= 3:
823 811 fsdecode = os.fsdecode
824 812 dest = os.path.join(
825 813 os.path.dirname(self.hgtarget),
826 814 fsdecode(dllbasename),
827 815 )
828 816
829 817 if not os.path.exists(dest):
830 818 shutil.copy(buf.value, dest)
831 819
832 820 if not pythonlib:
833 821 log.warn(
834 822 'could not determine Python DLL filename; assuming pythonXY'
835 823 )
836 824
837 825 hv = sys.hexversion
838 826 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
839 827
840 828 log.info('using %s as Python library name' % pythonlib)
841 829 with open('mercurial/hgpythonlib.h', 'wb') as f:
842 830 f.write(b'/* this file is autogenerated by setup.py */\n')
843 831 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
844 832
845 833 macros = None
846 834 if sys.version_info[0] >= 3:
847 835 macros = [('_UNICODE', None), ('UNICODE', None)]
848 836
849 837 objects = self.compiler.compile(
850 838 ['mercurial/exewrapper.c'],
851 839 output_dir=self.build_temp,
852 840 macros=macros,
853 841 )
854 842 self.compiler.link_executable(
855 843 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
856 844 )
857 845 if self.long_paths_support:
858 846 self.addlongpathsmanifest()
859 847
860 848 def addlongpathsmanifest(self):
861 849 r"""Add manifest pieces so that hg.exe understands long paths
862 850
863 851 This is an EXPERIMENTAL feature, use with care.
864 852 To enable long paths support, one needs to do two things:
865 853 - build Mercurial with --long-paths-support option
866 854 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
867 855 LongPathsEnabled to have value 1.
868 856
869 857 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
870 858 it happens because Mercurial uses mt.exe circa 2008, which is not
871 859 yet aware of long paths support in the manifest (I think so at least).
872 860 This does not stop mt.exe from embedding/merging the XML properly.
873 861
874 862 Why resource #1 should be used for .exe manifests? I don't know and
875 863 wasn't able to find an explanation for mortals. But it seems to work.
876 864 """
877 865 exefname = self.compiler.executable_filename(self.hgtarget)
878 866 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
879 867 os.close(fdauto)
880 868 with open(manfname, 'w') as f:
881 869 f.write(self.LONG_PATHS_MANIFEST)
882 870 log.info("long paths manifest is written to '%s'" % manfname)
883 871 inputresource = '-inputresource:%s;#1' % exefname
884 872 outputresource = '-outputresource:%s;#1' % exefname
885 873 log.info("running mt.exe to update hg.exe's manifest in-place")
886 874 # supplying both -manifest and -inputresource to mt.exe makes
887 875 # it merge the embedded and supplied manifests in the -outputresource
888 876 self.spawn(
889 877 [
890 878 'mt.exe',
891 879 '-nologo',
892 880 '-manifest',
893 881 manfname,
894 882 inputresource,
895 883 outputresource,
896 884 ]
897 885 )
898 886 log.info("done updating hg.exe's manifest")
899 887 os.remove(manfname)
900 888
901 889 @property
902 890 def hgexepath(self):
903 891 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
904 892 return os.path.join(self.build_temp, dir, 'hg.exe')
905 893
906 894
907 895 class hgbuilddoc(Command):
908 896 description = 'build documentation'
909 897 user_options = [
910 898 ('man', None, 'generate man pages'),
911 899 ('html', None, 'generate html pages'),
912 900 ]
913 901
914 902 def initialize_options(self):
915 903 self.man = None
916 904 self.html = None
917 905
918 906 def finalize_options(self):
919 907 # If --man or --html are set, only generate what we're told to.
920 908 # Otherwise generate everything.
921 909 have_subset = self.man is not None or self.html is not None
922 910
923 911 if have_subset:
924 912 self.man = True if self.man else False
925 913 self.html = True if self.html else False
926 914 else:
927 915 self.man = True
928 916 self.html = True
929 917
930 918 def run(self):
931 919 def normalizecrlf(p):
932 920 with open(p, 'rb') as fh:
933 921 orig = fh.read()
934 922
935 923 if b'\r\n' not in orig:
936 924 return
937 925
938 926 log.info('normalizing %s to LF line endings' % p)
939 927 with open(p, 'wb') as fh:
940 928 fh.write(orig.replace(b'\r\n', b'\n'))
941 929
942 930 def gentxt(root):
943 931 txt = 'doc/%s.txt' % root
944 932 log.info('generating %s' % txt)
945 933 res, out, err = runcmd(
946 934 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
947 935 )
948 936 if res:
949 937 raise SystemExit(
950 938 'error running gendoc.py: %s'
951 939 % '\n'.join([sysstr(out), sysstr(err)])
952 940 )
953 941
954 942 with open(txt, 'wb') as fh:
955 943 fh.write(out)
956 944
957 945 def gengendoc(root):
958 946 gendoc = 'doc/%s.gendoc.txt' % root
959 947
960 948 log.info('generating %s' % gendoc)
961 949 res, out, err = runcmd(
962 950 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
963 951 os.environ,
964 952 cwd='doc',
965 953 )
966 954 if res:
967 955 raise SystemExit(
968 956 'error running gendoc: %s'
969 957 % '\n'.join([sysstr(out), sysstr(err)])
970 958 )
971 959
972 960 with open(gendoc, 'wb') as fh:
973 961 fh.write(out)
974 962
975 963 def genman(root):
976 964 log.info('generating doc/%s' % root)
977 965 res, out, err = runcmd(
978 966 [
979 967 sys.executable,
980 968 'runrst',
981 969 'hgmanpage',
982 970 '--halt',
983 971 'warning',
984 972 '--strip-elements-with-class',
985 973 'htmlonly',
986 974 '%s.txt' % root,
987 975 root,
988 976 ],
989 977 os.environ,
990 978 cwd='doc',
991 979 )
992 980 if res:
993 981 raise SystemExit(
994 982 'error running runrst: %s'
995 983 % '\n'.join([sysstr(out), sysstr(err)])
996 984 )
997 985
998 986 normalizecrlf('doc/%s' % root)
999 987
1000 988 def genhtml(root):
1001 989 log.info('generating doc/%s.html' % root)
1002 990 res, out, err = runcmd(
1003 991 [
1004 992 sys.executable,
1005 993 'runrst',
1006 994 'html',
1007 995 '--halt',
1008 996 'warning',
1009 997 '--link-stylesheet',
1010 998 '--stylesheet-path',
1011 999 'style.css',
1012 1000 '%s.txt' % root,
1013 1001 '%s.html' % root,
1014 1002 ],
1015 1003 os.environ,
1016 1004 cwd='doc',
1017 1005 )
1018 1006 if res:
1019 1007 raise SystemExit(
1020 1008 'error running runrst: %s'
1021 1009 % '\n'.join([sysstr(out), sysstr(err)])
1022 1010 )
1023 1011
1024 1012 normalizecrlf('doc/%s.html' % root)
1025 1013
1026 1014 # This logic is duplicated in doc/Makefile.
1027 1015 sources = {
1028 1016 f
1029 1017 for f in os.listdir('mercurial/helptext')
1030 1018 if re.search(r'[0-9]\.txt$', f)
1031 1019 }
1032 1020
1033 1021 # common.txt is a one-off.
1034 1022 gentxt('common')
1035 1023
1036 1024 for source in sorted(sources):
1037 1025 assert source[-4:] == '.txt'
1038 1026 root = source[:-4]
1039 1027
1040 1028 gentxt(root)
1041 1029 gengendoc(root)
1042 1030
1043 1031 if self.man:
1044 1032 genman(root)
1045 1033 if self.html:
1046 1034 genhtml(root)
1047 1035
1048 1036
1049 1037 class hginstall(install):
1050 1038
1051 1039 user_options = install.user_options + [
1052 1040 (
1053 1041 'old-and-unmanageable',
1054 1042 None,
1055 1043 'noop, present for eggless setuptools compat',
1056 1044 ),
1057 1045 (
1058 1046 'single-version-externally-managed',
1059 1047 None,
1060 1048 'noop, present for eggless setuptools compat',
1061 1049 ),
1062 1050 ]
1063 1051
1064 1052 # Also helps setuptools not be sad while we refuse to create eggs.
1065 1053 single_version_externally_managed = True
1066 1054
1067 1055 def get_sub_commands(self):
1068 1056 # Screen out egg related commands to prevent egg generation. But allow
1069 1057 # mercurial.egg-info generation, since that is part of modern
1070 1058 # packaging.
1071 1059 excl = {'bdist_egg'}
1072 1060 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1073 1061
1074 1062
1075 1063 class hginstalllib(install_lib):
1076 1064 """
1077 1065 This is a specialization of install_lib that replaces the copy_file used
1078 1066 there so that it supports setting the mode of files after copying them,
1079 1067 instead of just preserving the mode that the files originally had. If your
1080 1068 system has a umask of something like 027, preserving the permissions when
1081 1069 copying will lead to a broken install.
1082 1070
1083 1071 Note that just passing keep_permissions=False to copy_file would be
1084 1072 insufficient, as it might still be applying a umask.
1085 1073 """
1086 1074
1087 1075 def run(self):
1088 1076 realcopyfile = file_util.copy_file
1089 1077
1090 1078 def copyfileandsetmode(*args, **kwargs):
1091 1079 src, dst = args[0], args[1]
1092 1080 dst, copied = realcopyfile(*args, **kwargs)
1093 1081 if copied:
1094 1082 st = os.stat(src)
1095 1083 # Persist executable bit (apply it to group and other if user
1096 1084 # has it)
1097 1085 if st[stat.ST_MODE] & stat.S_IXUSR:
1098 1086 setmode = int('0755', 8)
1099 1087 else:
1100 1088 setmode = int('0644', 8)
1101 1089 m = stat.S_IMODE(st[stat.ST_MODE])
1102 1090 m = (m & ~int('0777', 8)) | setmode
1103 1091 os.chmod(dst, m)
1104 1092
1105 1093 file_util.copy_file = copyfileandsetmode
1106 1094 try:
1107 1095 install_lib.run(self)
1108 1096 finally:
1109 1097 file_util.copy_file = realcopyfile
1110 1098
1111 1099
1112 1100 class hginstallscripts(install_scripts):
1113 1101 """
1114 1102 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1115 1103 the configured directory for modules. If possible, the path is made relative
1116 1104 to the directory for scripts.
1117 1105 """
1118 1106
1119 1107 def initialize_options(self):
1120 1108 install_scripts.initialize_options(self)
1121 1109
1122 1110 self.install_lib = None
1123 1111
1124 1112 def finalize_options(self):
1125 1113 install_scripts.finalize_options(self)
1126 1114 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1127 1115
1128 1116 def run(self):
1129 1117 install_scripts.run(self)
1130 1118
1131 1119 # It only makes sense to replace @LIBDIR@ with the install path if
1132 1120 # the install path is known. For wheels, the logic below calculates
1133 1121 # the libdir to be "../..". This is because the internal layout of a
1134 1122 # wheel archive looks like:
1135 1123 #
1136 1124 # mercurial-3.6.1.data/scripts/hg
1137 1125 # mercurial/__init__.py
1138 1126 #
1139 1127 # When installing wheels, the subdirectories of the "<pkg>.data"
1140 1128 # directory are translated to system local paths and files therein
1141 1129 # are copied in place. The mercurial/* files are installed into the
1142 1130 # site-packages directory. However, the site-packages directory
1143 1131 # isn't known until wheel install time. This means we have no clue
1144 1132 # at wheel generation time what the installed site-packages directory
1145 1133 # will be. And, wheels don't appear to provide the ability to register
1146 1134 # custom code to run during wheel installation. This all means that
1147 1135 # we can't reliably set the libdir in wheels: the default behavior
1148 1136 # of looking in sys.path must do.
1149 1137
1150 1138 if (
1151 1139 os.path.splitdrive(self.install_dir)[0]
1152 1140 != os.path.splitdrive(self.install_lib)[0]
1153 1141 ):
1154 1142 # can't make relative paths from one drive to another, so use an
1155 1143 # absolute path instead
1156 1144 libdir = self.install_lib
1157 1145 else:
1158 1146 libdir = os.path.relpath(self.install_lib, self.install_dir)
1159 1147
1160 1148 for outfile in self.outfiles:
1161 1149 with open(outfile, 'rb') as fp:
1162 1150 data = fp.read()
1163 1151
1164 1152 # skip binary files
1165 1153 if b'\0' in data:
1166 1154 continue
1167 1155
1168 1156 # During local installs, the shebang will be rewritten to the final
1169 1157 # install path. During wheel packaging, the shebang has a special
1170 1158 # value.
1171 1159 if data.startswith(b'#!python'):
1172 1160 log.info(
1173 1161 'not rewriting @LIBDIR@ in %s because install path '
1174 1162 'not known' % outfile
1175 1163 )
1176 1164 continue
1177 1165
1178 1166 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1179 1167 with open(outfile, 'wb') as fp:
1180 1168 fp.write(data)
1181 1169
1182 1170
1183 1171 # virtualenv installs custom distutils/__init__.py and
1184 1172 # distutils/distutils.cfg files which essentially proxy back to the
1185 1173 # "real" distutils in the main Python install. The presence of this
1186 1174 # directory causes py2exe to pick up the "hacked" distutils package
1187 1175 # from the virtualenv and "import distutils" will fail from the py2exe
1188 1176 # build because the "real" distutils files can't be located.
1189 1177 #
1190 1178 # We work around this by monkeypatching the py2exe code finding Python
1191 1179 # modules to replace the found virtualenv distutils modules with the
1192 1180 # original versions via filesystem scanning. This is a bit hacky. But
1193 1181 # it allows us to use virtualenvs for py2exe packaging, which is more
1194 1182 # deterministic and reproducible.
1195 1183 #
1196 1184 # It's worth noting that the common StackOverflow suggestions for this
1197 1185 # problem involve copying the original distutils files into the
1198 1186 # virtualenv or into the staging directory after setup() is invoked.
1199 1187 # The former is very brittle and can easily break setup(). Our hacking
1200 1188 # of the found modules routine has a similar result as copying the files
1201 1189 # manually. But it makes fewer assumptions about how py2exe works and
1202 1190 # is less brittle.
1203 1191
1204 1192 # This only catches virtualenvs made with virtualenv (as opposed to
1205 1193 # venv, which is likely what Python 3 uses).
1206 1194 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1207 1195
1208 1196 if py2exehacked:
1209 1197 from distutils.command.py2exe import py2exe as buildpy2exe
1210 1198 from py2exe.mf import Module as py2exemodule
1211 1199
1212 1200 class hgbuildpy2exe(buildpy2exe):
1213 1201 def find_needed_modules(self, mf, files, modules):
1214 1202 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1215 1203
1216 1204 # Replace virtualenv's distutils modules with the real ones.
1217 1205 modules = {}
1218 1206 for k, v in res.modules.items():
1219 1207 if k != 'distutils' and not k.startswith('distutils.'):
1220 1208 modules[k] = v
1221 1209
1222 1210 res.modules = modules
1223 1211
1224 1212 import opcode
1225 1213
1226 1214 distutilsreal = os.path.join(
1227 1215 os.path.dirname(opcode.__file__), 'distutils'
1228 1216 )
1229 1217
1230 1218 for root, dirs, files in os.walk(distutilsreal):
1231 1219 for f in sorted(files):
1232 1220 if not f.endswith('.py'):
1233 1221 continue
1234 1222
1235 1223 full = os.path.join(root, f)
1236 1224
1237 1225 parents = ['distutils']
1238 1226
1239 1227 if root != distutilsreal:
1240 1228 rel = os.path.relpath(root, distutilsreal)
1241 1229 parents.extend(p for p in rel.split(os.sep))
1242 1230
1243 1231 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1244 1232
1245 1233 if modname.startswith('distutils.tests.'):
1246 1234 continue
1247 1235
1248 1236 if modname.endswith('.__init__'):
1249 1237 modname = modname[: -len('.__init__')]
1250 1238 path = os.path.dirname(full)
1251 1239 else:
1252 1240 path = None
1253 1241
1254 1242 res.modules[modname] = py2exemodule(
1255 1243 modname, full, path=path
1256 1244 )
1257 1245
1258 1246 if 'distutils' not in res.modules:
1259 1247 raise SystemExit('could not find distutils modules')
1260 1248
1261 1249 return res
1262 1250
1263 1251
1264 1252 cmdclass = {
1265 1253 'build': hgbuild,
1266 1254 'build_doc': hgbuilddoc,
1267 1255 'build_mo': hgbuildmo,
1268 1256 'build_ext': hgbuildext,
1269 1257 'build_py': hgbuildpy,
1270 1258 'build_scripts': hgbuildscripts,
1271 1259 'build_hgextindex': buildhgextindex,
1272 1260 'install': hginstall,
1273 1261 'install_lib': hginstalllib,
1274 1262 'install_scripts': hginstallscripts,
1275 1263 'build_hgexe': buildhgexe,
1276 1264 }
1277 1265
1278 1266 if py2exehacked:
1279 1267 cmdclass['py2exe'] = hgbuildpy2exe
1280 1268
1281 1269 packages = [
1282 1270 'mercurial',
1283 1271 'mercurial.cext',
1284 1272 'mercurial.cffi',
1285 1273 'mercurial.defaultrc',
1286 1274 'mercurial.helptext',
1287 1275 'mercurial.helptext.internals',
1288 1276 'mercurial.hgweb',
1289 1277 'mercurial.interfaces',
1290 1278 'mercurial.pure',
1291 1279 'mercurial.templates',
1292 1280 'mercurial.thirdparty',
1293 1281 'mercurial.thirdparty.attr',
1294 1282 'mercurial.thirdparty.zope',
1295 1283 'mercurial.thirdparty.zope.interface',
1296 1284 'mercurial.upgrade_utils',
1297 1285 'mercurial.utils',
1298 1286 'mercurial.revlogutils',
1299 1287 'mercurial.testing',
1300 1288 'hgext',
1301 1289 'hgext.convert',
1302 1290 'hgext.fsmonitor',
1303 1291 'hgext.fastannotate',
1304 1292 'hgext.fsmonitor.pywatchman',
1305 1293 'hgext.git',
1306 1294 'hgext.highlight',
1307 1295 'hgext.hooklib',
1308 1296 'hgext.infinitepush',
1309 1297 'hgext.largefiles',
1310 1298 'hgext.lfs',
1311 1299 'hgext.narrow',
1312 1300 'hgext.remotefilelog',
1313 1301 'hgext.zeroconf',
1314 1302 'hgext3rd',
1315 1303 'hgdemandimport',
1316 1304 ]
1317 1305
1318 1306 # The pygit2 dependency dropped py2 support with the 1.0 release in Dec 2019.
1319 1307 # Prior releases do not build at all on Windows, because Visual Studio 2008
1320 1308 # doesn't understand C 11. Older Linux releases are buggy.
1321 1309 if sys.version_info[0] == 2:
1322 1310 packages.remove('hgext.git')
1323 1311
1324 1312
1325 1313 for name in os.listdir(os.path.join('mercurial', 'templates')):
1326 1314 if name != '__pycache__' and os.path.isdir(
1327 1315 os.path.join('mercurial', 'templates', name)
1328 1316 ):
1329 1317 packages.append('mercurial.templates.%s' % name)
1330 1318
1331 1319 if sys.version_info[0] == 2:
1332 1320 packages.extend(
1333 1321 [
1334 1322 'mercurial.thirdparty.concurrent',
1335 1323 'mercurial.thirdparty.concurrent.futures',
1336 1324 ]
1337 1325 )
1338 1326
1339 1327 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1340 1328 # py2exe can't cope with namespace packages very well, so we have to
1341 1329 # install any hgext3rd.* extensions that we want in the final py2exe
1342 1330 # image here. This is gross, but you gotta do what you gotta do.
1343 1331 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1344 1332
1345 1333 common_depends = [
1346 1334 'mercurial/bitmanipulation.h',
1347 1335 'mercurial/compat.h',
1348 1336 'mercurial/cext/util.h',
1349 1337 ]
1350 1338 common_include_dirs = ['mercurial']
1351 1339
1352 1340 common_cflags = []
1353 1341
1354 1342 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1355 1343 # makes declarations not at the top of a scope in the headers.
1356 1344 if os.name != 'nt' and sys.version_info[1] < 9:
1357 1345 common_cflags = ['-Werror=declaration-after-statement']
1358 1346
1359 1347 osutil_cflags = []
1360 1348 osutil_ldflags = []
1361 1349
1362 1350 # platform specific macros
1363 1351 for plat, func in [('bsd', 'setproctitle')]:
1364 1352 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1365 1353 osutil_cflags.append('-DHAVE_%s' % func.upper())
1366 1354
1367 1355 for plat, macro, code in [
1368 1356 (
1369 1357 'bsd|darwin',
1370 1358 'BSD_STATFS',
1371 1359 '''
1372 1360 #include <sys/param.h>
1373 1361 #include <sys/mount.h>
1374 1362 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1375 1363 ''',
1376 1364 ),
1377 1365 (
1378 1366 'linux',
1379 1367 'LINUX_STATFS',
1380 1368 '''
1381 1369 #include <linux/magic.h>
1382 1370 #include <sys/vfs.h>
1383 1371 int main() { struct statfs s; return sizeof(s.f_type); }
1384 1372 ''',
1385 1373 ),
1386 1374 ]:
1387 1375 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1388 1376 osutil_cflags.append('-DHAVE_%s' % macro)
1389 1377
1390 1378 if sys.platform == 'darwin':
1391 1379 osutil_ldflags += ['-framework', 'ApplicationServices']
1392 1380
1393 1381 if sys.platform == 'sunos5':
1394 1382 osutil_ldflags += ['-lsocket']
1395 1383
1396 1384 xdiff_srcs = [
1397 1385 'mercurial/thirdparty/xdiff/xdiffi.c',
1398 1386 'mercurial/thirdparty/xdiff/xprepare.c',
1399 1387 'mercurial/thirdparty/xdiff/xutils.c',
1400 1388 ]
1401 1389
1402 1390 xdiff_headers = [
1403 1391 'mercurial/thirdparty/xdiff/xdiff.h',
1404 1392 'mercurial/thirdparty/xdiff/xdiffi.h',
1405 1393 'mercurial/thirdparty/xdiff/xinclude.h',
1406 1394 'mercurial/thirdparty/xdiff/xmacros.h',
1407 1395 'mercurial/thirdparty/xdiff/xprepare.h',
1408 1396 'mercurial/thirdparty/xdiff/xtypes.h',
1409 1397 'mercurial/thirdparty/xdiff/xutils.h',
1410 1398 ]
1411 1399
1412 1400
1413 1401 class RustCompilationError(CCompilerError):
1414 1402 """Exception class for Rust compilation errors."""
1415 1403
1416 1404
1417 1405 class RustExtension(Extension):
1418 1406 """Base classes for concrete Rust Extension classes."""
1419 1407
1420 1408 rusttargetdir = os.path.join('rust', 'target', 'release')
1421 1409
1422 1410 def __init__(
1423 1411 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1424 1412 ):
1425 1413 Extension.__init__(self, mpath, sources, **kw)
1426 1414 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1427 1415 self.py3_features = py3_features
1428 1416
1429 1417 # adding Rust source and control files to depends so that the extension
1430 1418 # gets rebuilt if they've changed
1431 1419 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1432 1420 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1433 1421 if os.path.exists(cargo_lock):
1434 1422 self.depends.append(cargo_lock)
1435 1423 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1436 1424 self.depends.extend(
1437 1425 os.path.join(dirpath, fname)
1438 1426 for fname in fnames
1439 1427 if os.path.splitext(fname)[1] == '.rs'
1440 1428 )
1441 1429
1442 1430 @staticmethod
1443 1431 def rustdylibsuffix():
1444 1432 """Return the suffix for shared libraries produced by rustc.
1445 1433
1446 1434 See also: https://doc.rust-lang.org/reference/linkage.html
1447 1435 """
1448 1436 if sys.platform == 'darwin':
1449 1437 return '.dylib'
1450 1438 elif os.name == 'nt':
1451 1439 return '.dll'
1452 1440 else:
1453 1441 return '.so'
1454 1442
1455 1443 def rustbuild(self):
1456 1444 env = os.environ.copy()
1457 1445 if 'HGTEST_RESTOREENV' in env:
1458 1446 # Mercurial tests change HOME to a temporary directory,
1459 1447 # but, if installed with rustup, the Rust toolchain needs
1460 1448 # HOME to be correct (otherwise the 'no default toolchain'
1461 1449 # error message is issued and the build fails).
1462 1450 # This happens currently with test-hghave.t, which does
1463 1451 # invoke this build.
1464 1452
1465 1453 # Unix only fix (os.path.expanduser not really reliable if
1466 1454 # HOME is shadowed like this)
1467 1455 import pwd
1468 1456
1469 1457 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1470 1458
1471 1459 cargocmd = ['cargo', 'rustc', '--release']
1472 1460
1473 1461 feature_flags = []
1474 1462
1475 1463 if sys.version_info[0] == 3 and self.py3_features is not None:
1476 1464 feature_flags.append(self.py3_features)
1477 1465 cargocmd.append('--no-default-features')
1478 1466
1479 1467 rust_features = env.get("HG_RUST_FEATURES")
1480 1468 if rust_features:
1481 1469 feature_flags.append(rust_features)
1482 1470
1483 1471 cargocmd.extend(('--features', " ".join(feature_flags)))
1484 1472
1485 1473 cargocmd.append('--')
1486 1474 if sys.platform == 'darwin':
1487 1475 cargocmd.extend(
1488 1476 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1489 1477 )
1490 1478 try:
1491 1479 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1492 1480 except OSError as exc:
1493 1481 if exc.errno == errno.ENOENT:
1494 1482 raise RustCompilationError("Cargo not found")
1495 1483 elif exc.errno == errno.EACCES:
1496 1484 raise RustCompilationError(
1497 1485 "Cargo found, but permisssion to execute it is denied"
1498 1486 )
1499 1487 else:
1500 1488 raise
1501 1489 except subprocess.CalledProcessError:
1502 1490 raise RustCompilationError(
1503 1491 "Cargo failed. Working directory: %r, "
1504 1492 "command: %r, environment: %r"
1505 1493 % (self.rustsrcdir, cargocmd, env)
1506 1494 )
1507 1495
1508 1496
1509 1497 class RustStandaloneExtension(RustExtension):
1510 1498 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1511 1499 RustExtension.__init__(
1512 1500 self, pydottedname, [], dylibname, rustcrate, **kw
1513 1501 )
1514 1502 self.dylibname = dylibname
1515 1503
1516 1504 def build(self, target_dir):
1517 1505 self.rustbuild()
1518 1506 target = [target_dir]
1519 1507 target.extend(self.name.split('.'))
1520 1508 target[-1] += DYLIB_SUFFIX
1521 1509 shutil.copy2(
1522 1510 os.path.join(
1523 1511 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1524 1512 ),
1525 1513 os.path.join(*target),
1526 1514 )
1527 1515
1528 1516
1529 1517 extmodules = [
1530 1518 Extension(
1531 1519 'mercurial.cext.base85',
1532 1520 ['mercurial/cext/base85.c'],
1533 1521 include_dirs=common_include_dirs,
1534 1522 extra_compile_args=common_cflags,
1535 1523 depends=common_depends,
1536 1524 ),
1537 1525 Extension(
1538 1526 'mercurial.cext.bdiff',
1539 1527 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1540 1528 include_dirs=common_include_dirs,
1541 1529 extra_compile_args=common_cflags,
1542 1530 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1543 1531 ),
1544 1532 Extension(
1545 1533 'mercurial.cext.mpatch',
1546 1534 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1547 1535 include_dirs=common_include_dirs,
1548 1536 extra_compile_args=common_cflags,
1549 1537 depends=common_depends,
1550 1538 ),
1551 1539 Extension(
1552 1540 'mercurial.cext.parsers',
1553 1541 [
1554 1542 'mercurial/cext/charencode.c',
1555 1543 'mercurial/cext/dirs.c',
1556 1544 'mercurial/cext/manifest.c',
1557 1545 'mercurial/cext/parsers.c',
1558 1546 'mercurial/cext/pathencode.c',
1559 1547 'mercurial/cext/revlog.c',
1560 1548 ],
1561 1549 include_dirs=common_include_dirs,
1562 1550 extra_compile_args=common_cflags,
1563 1551 depends=common_depends
1564 1552 + [
1565 1553 'mercurial/cext/charencode.h',
1566 1554 'mercurial/cext/revlog.h',
1567 1555 ],
1568 1556 ),
1569 1557 Extension(
1570 1558 'mercurial.cext.osutil',
1571 1559 ['mercurial/cext/osutil.c'],
1572 1560 include_dirs=common_include_dirs,
1573 1561 extra_compile_args=common_cflags + osutil_cflags,
1574 1562 extra_link_args=osutil_ldflags,
1575 1563 depends=common_depends,
1576 1564 ),
1577 1565 Extension(
1578 1566 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1579 1567 [
1580 1568 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1581 1569 ],
1582 1570 extra_compile_args=common_cflags,
1583 1571 ),
1584 1572 Extension(
1585 1573 'mercurial.thirdparty.sha1dc',
1586 1574 [
1587 1575 'mercurial/thirdparty/sha1dc/cext.c',
1588 1576 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1589 1577 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1590 1578 ],
1591 1579 extra_compile_args=common_cflags,
1592 1580 ),
1593 1581 Extension(
1594 1582 'hgext.fsmonitor.pywatchman.bser',
1595 1583 ['hgext/fsmonitor/pywatchman/bser.c'],
1596 1584 extra_compile_args=common_cflags,
1597 1585 ),
1598 1586 RustStandaloneExtension(
1599 1587 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1600 1588 ),
1601 1589 ]
1602 1590
1603 1591
1604 1592 sys.path.insert(0, 'contrib/python-zstandard')
1605 1593 import setup_zstd
1606 1594
1607 1595 zstd = setup_zstd.get_c_extension(
1608 1596 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1609 1597 )
1610 1598 zstd.extra_compile_args += common_cflags
1611 1599 extmodules.append(zstd)
1612 1600
1613 1601 try:
1614 1602 from distutils import cygwinccompiler
1615 1603
1616 1604 # the -mno-cygwin option has been deprecated for years
1617 1605 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1618 1606
1619 1607 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1620 1608 def __init__(self, *args, **kwargs):
1621 1609 mingw32compilerclass.__init__(self, *args, **kwargs)
1622 1610 for i in 'compiler compiler_so linker_exe linker_so'.split():
1623 1611 try:
1624 1612 getattr(self, i).remove('-mno-cygwin')
1625 1613 except ValueError:
1626 1614 pass
1627 1615
1628 1616 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1629 1617 except ImportError:
1630 1618 # the cygwinccompiler package is not available on some Python
1631 1619 # distributions like the ones from the optware project for Synology
1632 1620 # DiskStation boxes
1633 1621 class HackedMingw32CCompiler(object):
1634 1622 pass
1635 1623
1636 1624
1637 1625 if os.name == 'nt':
1638 1626 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1639 1627 # extra_link_args to distutils.extensions.Extension() doesn't have any
1640 1628 # effect.
1641 1629 from distutils import msvccompiler
1642 1630
1643 1631 msvccompilerclass = msvccompiler.MSVCCompiler
1644 1632
1645 1633 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1646 1634 def initialize(self):
1647 1635 msvccompilerclass.initialize(self)
1648 1636 # "warning LNK4197: export 'func' specified multiple times"
1649 1637 self.ldflags_shared.append('/ignore:4197')
1650 1638 self.ldflags_shared_debug.append('/ignore:4197')
1651 1639
1652 1640 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1653 1641
1654 1642 packagedata = {
1655 1643 'mercurial': [
1656 1644 'locale/*/LC_MESSAGES/hg.mo',
1657 1645 'dummycert.pem',
1658 1646 ],
1659 1647 'mercurial.defaultrc': [
1660 1648 '*.rc',
1661 1649 ],
1662 1650 'mercurial.helptext': [
1663 1651 '*.txt',
1664 1652 ],
1665 1653 'mercurial.helptext.internals': [
1666 1654 '*.txt',
1667 1655 ],
1668 1656 }
1669 1657
1670 1658
1671 1659 def ordinarypath(p):
1672 1660 return p and p[0] != '.' and p[-1] != '~'
1673 1661
1674 1662
1675 1663 for root in ('templates',):
1676 1664 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1677 1665 packagename = curdir.replace(os.sep, '.')
1678 1666 packagedata[packagename] = list(filter(ordinarypath, files))
1679 1667
1680 1668 datafiles = []
1681 1669
1682 1670 # distutils expects version to be str/unicode. Converting it to
1683 1671 # unicode on Python 2 still works because it won't contain any
1684 1672 # non-ascii bytes and will be implicitly converted back to bytes
1685 1673 # when operated on.
1686 assert isinstance(version, bytes)
1687 setupversion = version.decode('ascii')
1674 assert isinstance(version, str)
1675 setupversion = version
1688 1676
1689 1677 extra = {}
1690 1678
1691 1679 py2exepackages = [
1692 1680 'hgdemandimport',
1693 1681 'hgext3rd',
1694 1682 'hgext',
1695 1683 'email',
1696 1684 # implicitly imported per module policy
1697 1685 # (cffi wouldn't be used as a frozen exe)
1698 1686 'mercurial.cext',
1699 1687 #'mercurial.cffi',
1700 1688 'mercurial.pure',
1701 1689 ]
1702 1690
1703 1691 py2exe_includes = []
1704 1692
1705 1693 py2exeexcludes = []
1706 1694 py2exedllexcludes = ['crypt32.dll']
1707 1695
1708 1696 if issetuptools:
1709 1697 extra['python_requires'] = supportedpy
1710 1698
1711 1699 if py2exeloaded:
1712 1700 extra['console'] = [
1713 1701 {
1714 1702 'script': 'hg',
1715 1703 'copyright': 'Copyright (C) 2005-2021 Matt Mackall and others',
1716 1704 'product_version': version,
1717 1705 }
1718 1706 ]
1719 1707 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1720 1708 # Need to override hgbuild because it has a private copy of
1721 1709 # build.sub_commands.
1722 1710 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1723 1711 # put dlls in sub directory so that they won't pollute PATH
1724 1712 extra['zipfile'] = 'lib/library.zip'
1725 1713
1726 1714 # We allow some configuration to be supplemented via environment
1727 1715 # variables. This is better than setup.cfg files because it allows
1728 1716 # supplementing configs instead of replacing them.
1729 1717 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1730 1718 if extrapackages:
1731 1719 py2exepackages.extend(extrapackages.split(' '))
1732 1720
1733 1721 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1734 1722 if extra_includes:
1735 1723 py2exe_includes.extend(extra_includes.split(' '))
1736 1724
1737 1725 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1738 1726 if excludes:
1739 1727 py2exeexcludes.extend(excludes.split(' '))
1740 1728
1741 1729 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1742 1730 if dllexcludes:
1743 1731 py2exedllexcludes.extend(dllexcludes.split(' '))
1744 1732
1745 1733 if os.environ.get('PYOXIDIZER'):
1746 1734 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1747 1735
1748 1736 if os.name == 'nt':
1749 1737 # Windows binary file versions for exe/dll files must have the
1750 1738 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1751 1739 setupversion = setupversion.split(r'+', 1)[0]
1752 1740
1753 1741 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1754 1742 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1755 1743 if version:
1756 1744 version = version[0]
1757 1745 if sys.version_info[0] == 3:
1758 1746 version = version.decode('utf-8')
1759 1747 xcode4 = version.startswith('Xcode') and StrictVersion(
1760 1748 version.split()[1]
1761 1749 ) >= StrictVersion('4.0')
1762 1750 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1763 1751 else:
1764 1752 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1765 1753 # installed, but instead with only command-line tools. Assume
1766 1754 # that only happens on >= Lion, thus no PPC support.
1767 1755 xcode4 = True
1768 1756 xcode51 = False
1769 1757
1770 1758 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1771 1759 # distutils.sysconfig
1772 1760 if xcode4:
1773 1761 os.environ['ARCHFLAGS'] = ''
1774 1762
1775 1763 # XCode 5.1 changes clang such that it now fails to compile if the
1776 1764 # -mno-fused-madd flag is passed, but the version of Python shipped with
1777 1765 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1778 1766 # C extension modules, and a bug has been filed upstream at
1779 1767 # http://bugs.python.org/issue21244. We also need to patch this here
1780 1768 # so Mercurial can continue to compile in the meantime.
1781 1769 if xcode51:
1782 1770 cflags = get_config_var('CFLAGS')
1783 1771 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1784 1772 os.environ['CFLAGS'] = (
1785 1773 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1786 1774 )
1787 1775
1788 1776 setup(
1789 1777 name='mercurial',
1790 1778 version=setupversion,
1791 1779 author='Matt Mackall and many others',
1792 1780 author_email='mercurial@mercurial-scm.org',
1793 1781 url='https://mercurial-scm.org/',
1794 1782 download_url='https://mercurial-scm.org/release/',
1795 1783 description=(
1796 1784 'Fast scalable distributed SCM (revision control, version '
1797 1785 'control) system'
1798 1786 ),
1799 1787 long_description=(
1800 1788 'Mercurial is a distributed SCM tool written in Python.'
1801 1789 ' It is used by a number of large projects that require'
1802 1790 ' fast, reliable distributed revision control, such as '
1803 1791 'Mozilla.'
1804 1792 ),
1805 1793 license='GNU GPLv2 or any later version',
1806 1794 classifiers=[
1807 1795 'Development Status :: 6 - Mature',
1808 1796 'Environment :: Console',
1809 1797 'Intended Audience :: Developers',
1810 1798 'Intended Audience :: System Administrators',
1811 1799 'License :: OSI Approved :: GNU General Public License (GPL)',
1812 1800 'Natural Language :: Danish',
1813 1801 'Natural Language :: English',
1814 1802 'Natural Language :: German',
1815 1803 'Natural Language :: Italian',
1816 1804 'Natural Language :: Japanese',
1817 1805 'Natural Language :: Portuguese (Brazilian)',
1818 1806 'Operating System :: Microsoft :: Windows',
1819 1807 'Operating System :: OS Independent',
1820 1808 'Operating System :: POSIX',
1821 1809 'Programming Language :: C',
1822 1810 'Programming Language :: Python',
1823 1811 'Topic :: Software Development :: Version Control',
1824 1812 ],
1825 1813 scripts=scripts,
1826 1814 packages=packages,
1827 1815 ext_modules=extmodules,
1828 1816 data_files=datafiles,
1829 1817 package_data=packagedata,
1830 1818 cmdclass=cmdclass,
1831 1819 distclass=hgdist,
1832 1820 options={
1833 1821 'py2exe': {
1834 1822 'bundle_files': 3,
1835 1823 'dll_excludes': py2exedllexcludes,
1836 1824 'includes': py2exe_includes,
1837 1825 'excludes': py2exeexcludes,
1838 1826 'packages': py2exepackages,
1839 1827 },
1840 1828 'bdist_mpkg': {
1841 1829 'zipdist': False,
1842 1830 'license': 'COPYING',
1843 1831 'readme': 'contrib/packaging/macosx/Readme.html',
1844 1832 'welcome': 'contrib/packaging/macosx/Welcome.html',
1845 1833 },
1846 1834 },
1847 1835 **extra
1848 1836 )
General Comments 0
You need to be logged in to leave comments. Login now