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