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