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