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