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