Patchwork D7433: procutil: move mainfrozen() to new resourceutil.py

login
register
mail settings
Submitter phabricator
Date Nov. 16, 2019, 7:24 p.m.
Message ID <154d6f682b6a0bcda7e2f7d2fb15925f@localhost.localdomain>
Download mbox | patch
Permalink /patch/43309/
State Not Applicable
Headers show

Comments

phabricator - Nov. 16, 2019, 7:24 p.m.
Closed by commit rHGb5554a088d20: procutil: move mainfrozen() to new resourceutil.py (authored by martinvonz).
This revision was automatically updated to reflect the committed changes.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7433?vs=18166&id=18182

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7433/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7433

AFFECTED FILES
  mercurial/hook.py
  mercurial/sslutil.py
  mercurial/util.py
  mercurial/utils/procutil.py
  mercurial/utils/resourceutil.py

CHANGE DETAILS




To: martinvonz, #hg-reviewers, indygreg
Cc: indygreg, mjpieters, mercurial-devel

Patch

diff --git a/mercurial/utils/procutil.py b/mercurial/utils/resourceutil.py
copy from mercurial/utils/procutil.py
copy to mercurial/utils/resourceutil.py
--- a/mercurial/utils/procutil.py
+++ b/mercurial/utils/resourceutil.py
@@ -1,4 +1,4 @@ 
-# procutil.py - utility for managing processes and executable environment
+# resourceutil.py - utility for looking up resources
 #
 #  Copyright 2005 K. Thananchayan <thananck@yahoo.com>
 #  Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
@@ -9,251 +9,13 @@ 
 
 from __future__ import absolute_import
 
-import contextlib
-import errno
 import imp
-import io
-import os
-import signal
-import subprocess
 import sys
-import time
-
-from ..i18n import _
-from ..pycompat import (
-    getattr,
-    open,
-)
 
 from .. import (
-    encoding,
-    error,
-    policy,
     pycompat,
 )
 
-osutil = policy.importmod('osutil')
-
-stderr = pycompat.stderr
-stdin = pycompat.stdin
-stdout = pycompat.stdout
-
-
-def isatty(fp):
-    try:
-        return fp.isatty()
-    except AttributeError:
-        return False
-
-
-# glibc determines buffering on first write to stdout - if we replace a TTY
-# destined stdout with a pipe destined stdout (e.g. pager), we want line
-# buffering (or unbuffered, on Windows)
-if isatty(stdout):
-    if pycompat.iswindows:
-        # Windows doesn't support line buffering
-        stdout = os.fdopen(stdout.fileno(), 'wb', 0)
-    elif not pycompat.ispy3:
-        # on Python 3, stdout (sys.stdout.buffer) is already line buffered and
-        # buffering=1 is not handled in binary mode
-        stdout = os.fdopen(stdout.fileno(), 'wb', 1)
-
-if pycompat.iswindows:
-    from .. import windows as platform
-
-    stdout = platform.winstdout(stdout)
-else:
-    from .. import posix as platform
-
-findexe = platform.findexe
-_gethgcmd = platform.gethgcmd
-getuser = platform.getuser
-getpid = os.getpid
-hidewindow = platform.hidewindow
-quotecommand = platform.quotecommand
-readpipe = platform.readpipe
-setbinary = platform.setbinary
-setsignalhandler = platform.setsignalhandler
-shellquote = platform.shellquote
-shellsplit = platform.shellsplit
-spawndetached = platform.spawndetached
-sshargs = platform.sshargs
-testpid = platform.testpid
-
-try:
-    setprocname = osutil.setprocname
-except AttributeError:
-    pass
-try:
-    unblocksignal = osutil.unblocksignal
-except AttributeError:
-    pass
-
-closefds = pycompat.isposix
-
-
-def explainexit(code):
-    """return a message describing a subprocess status
-    (codes from kill are negative - not os.system/wait encoding)"""
-    if code >= 0:
-        return _(b"exited with status %d") % code
-    return _(b"killed by signal %d") % -code
-
-
-class _pfile(object):
-    """File-like wrapper for a stream opened by subprocess.Popen()"""
-
-    def __init__(self, proc, fp):
-        self._proc = proc
-        self._fp = fp
-
-    def close(self):
-        # unlike os.popen(), this returns an integer in subprocess coding
-        self._fp.close()
-        return self._proc.wait()
-
-    def __iter__(self):
-        return iter(self._fp)
-
-    def __getattr__(self, attr):
-        return getattr(self._fp, attr)
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, exc_type, exc_value, exc_tb):
-        self.close()
-
-
-def popen(cmd, mode=b'rb', bufsize=-1):
-    if mode == b'rb':
-        return _popenreader(cmd, bufsize)
-    elif mode == b'wb':
-        return _popenwriter(cmd, bufsize)
-    raise error.ProgrammingError(b'unsupported mode: %r' % mode)
-
-
-def _popenreader(cmd, bufsize):
-    p = subprocess.Popen(
-        tonativestr(quotecommand(cmd)),
-        shell=True,
-        bufsize=bufsize,
-        close_fds=closefds,
-        stdout=subprocess.PIPE,
-    )
-    return _pfile(p, p.stdout)
-
-
-def _popenwriter(cmd, bufsize):
-    p = subprocess.Popen(
-        tonativestr(quotecommand(cmd)),
-        shell=True,
-        bufsize=bufsize,
-        close_fds=closefds,
-        stdin=subprocess.PIPE,
-    )
-    return _pfile(p, p.stdin)
-
-
-def popen2(cmd, env=None):
-    # Setting bufsize to -1 lets the system decide the buffer size.
-    # The default for bufsize is 0, meaning unbuffered. This leads to
-    # poor performance on Mac OS X: http://bugs.python.org/issue4194
-    p = subprocess.Popen(
-        tonativestr(cmd),
-        shell=True,
-        bufsize=-1,
-        close_fds=closefds,
-        stdin=subprocess.PIPE,
-        stdout=subprocess.PIPE,
-        env=tonativeenv(env),
-    )
-    return p.stdin, p.stdout
-
-
-def popen3(cmd, env=None):
-    stdin, stdout, stderr, p = popen4(cmd, env)
-    return stdin, stdout, stderr
-
-
-def popen4(cmd, env=None, bufsize=-1):
-    p = subprocess.Popen(
-        tonativestr(cmd),
-        shell=True,
-        bufsize=bufsize,
-        close_fds=closefds,
-        stdin=subprocess.PIPE,
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE,
-        env=tonativeenv(env),
-    )
-    return p.stdin, p.stdout, p.stderr, p
-
-
-def pipefilter(s, cmd):
-    '''filter string S through command CMD, returning its output'''
-    p = subprocess.Popen(
-        tonativestr(cmd),
-        shell=True,
-        close_fds=closefds,
-        stdin=subprocess.PIPE,
-        stdout=subprocess.PIPE,
-    )
-    pout, perr = p.communicate(s)
-    return pout
-
-
-def tempfilter(s, cmd):
-    '''filter string S through a pair of temporary files with CMD.
-    CMD is used as a template to create the real command to be run,
-    with the strings INFILE and OUTFILE replaced by the real names of
-    the temporary files generated.'''
-    inname, outname = None, None
-    try:
-        infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
-        fp = os.fdopen(infd, 'wb')
-        fp.write(s)
-        fp.close()
-        outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
-        os.close(outfd)
-        cmd = cmd.replace(b'INFILE', inname)
-        cmd = cmd.replace(b'OUTFILE', outname)
-        code = system(cmd)
-        if pycompat.sysplatform == b'OpenVMS' and code & 1:
-            code = 0
-        if code:
-            raise error.Abort(
-                _(b"command '%s' failed: %s") % (cmd, explainexit(code))
-            )
-        with open(outname, b'rb') as fp:
-            return fp.read()
-    finally:
-        try:
-            if inname:
-                os.unlink(inname)
-        except OSError:
-            pass
-        try:
-            if outname:
-                os.unlink(outname)
-        except OSError:
-            pass
-
-
-_filtertable = {
-    b'tempfile:': tempfilter,
-    b'pipe:': pipefilter,
-}
-
-
-def filter(s, cmd):
-    b"filter a string through a command that transforms its input to its output"
-    for name, fn in pycompat.iteritems(_filtertable):
-        if cmd.startswith(name):
-            return fn(s, cmd[len(name) :].lstrip())
-    return pipefilter(s, cmd)
-
-
 def mainfrozen():
     """return True if we are a frozen executable.
 
@@ -265,373 +27,3 @@ 
         or pycompat.safehasattr(sys, "importers")  # new py2exe
         or imp.is_frozen("__main__")  # old py2exe
     )  # tools/freeze
-
-
-_hgexecutable = None
-
-
-def hgexecutable():
-    """return location of the 'hg' executable.
-
-    Defaults to $HG or 'hg' in the search path.
-    """
-    if _hgexecutable is None:
-        hg = encoding.environ.get(b'HG')
-        mainmod = sys.modules['__main__']
-        if hg:
-            _sethgexecutable(hg)
-        elif mainfrozen():
-            if getattr(sys, 'frozen', None) == 'macosx_app':
-                # Env variable set by py2app
-                _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
-            else:
-                _sethgexecutable(pycompat.sysexecutable)
-        elif (
-            not pycompat.iswindows
-            and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
-        ):
-            _sethgexecutable(pycompat.fsencode(mainmod.__file__))
-        else:
-            _sethgexecutable(
-                findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
-            )
-    return _hgexecutable
-
-
-def _sethgexecutable(path):
-    """set location of the 'hg' executable"""
-    global _hgexecutable
-    _hgexecutable = path
-
-
-def _testfileno(f, stdf):
-    fileno = getattr(f, 'fileno', None)
-    try:
-        return fileno and fileno() == stdf.fileno()
-    except io.UnsupportedOperation:
-        return False  # fileno() raised UnsupportedOperation
-
-
-def isstdin(f):
-    return _testfileno(f, sys.__stdin__)
-
-
-def isstdout(f):
-    return _testfileno(f, sys.__stdout__)
-
-
-def protectstdio(uin, uout):
-    """Duplicate streams and redirect original if (uin, uout) are stdio
-
-    If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
-    redirected to stderr so the output is still readable.
-
-    Returns (fin, fout) which point to the original (uin, uout) fds, but
-    may be copy of (uin, uout). The returned streams can be considered
-    "owned" in that print(), exec(), etc. never reach to them.
-    """
-    uout.flush()
-    fin, fout = uin, uout
-    if _testfileno(uin, stdin):
-        newfd = os.dup(uin.fileno())
-        nullfd = os.open(os.devnull, os.O_RDONLY)
-        os.dup2(nullfd, uin.fileno())
-        os.close(nullfd)
-        fin = os.fdopen(newfd, 'rb')
-    if _testfileno(uout, stdout):
-        newfd = os.dup(uout.fileno())
-        os.dup2(stderr.fileno(), uout.fileno())
-        fout = os.fdopen(newfd, 'wb')
-    return fin, fout
-
-
-def restorestdio(uin, uout, fin, fout):
-    """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
-    uout.flush()
-    for f, uif in [(fin, uin), (fout, uout)]:
-        if f is not uif:
-            os.dup2(f.fileno(), uif.fileno())
-            f.close()
-
-
-def shellenviron(environ=None):
-    """return environ with optional override, useful for shelling out"""
-
-    def py2shell(val):
-        b'convert python object into string that is useful to shell'
-        if val is None or val is False:
-            return b'0'
-        if val is True:
-            return b'1'
-        return pycompat.bytestr(val)
-
-    env = dict(encoding.environ)
-    if environ:
-        env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
-    env[b'HG'] = hgexecutable()
-    return env
-
-
-if pycompat.iswindows:
-
-    def shelltonative(cmd, env):
-        return platform.shelltocmdexe(  # pytype: disable=module-attr
-            cmd, shellenviron(env)
-        )
-
-    tonativestr = encoding.strfromlocal
-else:
-
-    def shelltonative(cmd, env):
-        return cmd
-
-    tonativestr = pycompat.identity
-
-
-def tonativeenv(env):
-    '''convert the environment from bytes to strings suitable for Popen(), etc.
-    '''
-    return pycompat.rapply(tonativestr, env)
-
-
-def system(cmd, environ=None, cwd=None, out=None):
-    '''enhanced shell command execution.
-    run with environment maybe modified, maybe in different dir.
-
-    if out is specified, it is assumed to be a file-like object that has a
-    write() method. stdout and stderr will be redirected to out.'''
-    try:
-        stdout.flush()
-    except Exception:
-        pass
-    cmd = quotecommand(cmd)
-    env = shellenviron(environ)
-    if out is None or isstdout(out):
-        rc = subprocess.call(
-            tonativestr(cmd),
-            shell=True,
-            close_fds=closefds,
-            env=tonativeenv(env),
-            cwd=pycompat.rapply(tonativestr, cwd),
-        )
-    else:
-        proc = subprocess.Popen(
-            tonativestr(cmd),
-            shell=True,
-            close_fds=closefds,
-            env=tonativeenv(env),
-            cwd=pycompat.rapply(tonativestr, cwd),
-            stdout=subprocess.PIPE,
-            stderr=subprocess.STDOUT,
-        )
-        for line in iter(proc.stdout.readline, b''):
-            out.write(line)
-        proc.wait()
-        rc = proc.returncode
-    if pycompat.sysplatform == b'OpenVMS' and rc & 1:
-        rc = 0
-    return rc
-
-
-def gui():
-    '''Are we running in a GUI?'''
-    if pycompat.isdarwin:
-        if b'SSH_CONNECTION' in encoding.environ:
-            # handle SSH access to a box where the user is logged in
-            return False
-        elif getattr(osutil, 'isgui', None):
-            # check if a CoreGraphics session is available
-            return osutil.isgui()
-        else:
-            # pure build; use a safe default
-            return True
-    else:
-        return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
-
-
-def hgcmd():
-    """Return the command used to execute current hg
-
-    This is different from hgexecutable() because on Windows we want
-    to avoid things opening new shell windows like batch files, so we
-    get either the python call or current executable.
-    """
-    if mainfrozen():
-        if getattr(sys, 'frozen', None) == 'macosx_app':
-            # Env variable set by py2app
-            return [encoding.environ[b'EXECUTABLEPATH']]
-        else:
-            return [pycompat.sysexecutable]
-    return _gethgcmd()
-
-
-def rundetached(args, condfn):
-    """Execute the argument list in a detached process.
-
-    condfn is a callable which is called repeatedly and should return
-    True once the child process is known to have started successfully.
-    At this point, the child process PID is returned. If the child
-    process fails to start or finishes before condfn() evaluates to
-    True, return -1.
-    """
-    # Windows case is easier because the child process is either
-    # successfully starting and validating the condition or exiting
-    # on failure. We just poll on its PID. On Unix, if the child
-    # process fails to start, it will be left in a zombie state until
-    # the parent wait on it, which we cannot do since we expect a long
-    # running process on success. Instead we listen for SIGCHLD telling
-    # us our child process terminated.
-    terminated = set()
-
-    def handler(signum, frame):
-        terminated.add(os.wait())
-
-    prevhandler = None
-    SIGCHLD = getattr(signal, 'SIGCHLD', None)
-    if SIGCHLD is not None:
-        prevhandler = signal.signal(SIGCHLD, handler)
-    try:
-        pid = spawndetached(args)
-        while not condfn():
-            if (pid in terminated or not testpid(pid)) and not condfn():
-                return -1
-            time.sleep(0.1)
-        return pid
-    finally:
-        if prevhandler is not None:
-            signal.signal(signal.SIGCHLD, prevhandler)
-
-
-@contextlib.contextmanager
-def uninterruptible(warn):
-    """Inhibit SIGINT handling on a region of code.
-
-    Note that if this is called in a non-main thread, it turns into a no-op.
-
-    Args:
-      warn: A callable which takes no arguments, and returns True if the
-            previous signal handling should be restored.
-    """
-
-    oldsiginthandler = [signal.getsignal(signal.SIGINT)]
-    shouldbail = []
-
-    def disabledsiginthandler(*args):
-        if warn():
-            signal.signal(signal.SIGINT, oldsiginthandler[0])
-            del oldsiginthandler[0]
-        shouldbail.append(True)
-
-    try:
-        try:
-            signal.signal(signal.SIGINT, disabledsiginthandler)
-        except ValueError:
-            # wrong thread, oh well, we tried
-            del oldsiginthandler[0]
-        yield
-    finally:
-        if oldsiginthandler:
-            signal.signal(signal.SIGINT, oldsiginthandler[0])
-        if shouldbail:
-            raise KeyboardInterrupt
-
-
-if pycompat.iswindows:
-    # no fork on Windows, but we can create a detached process
-    # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
-    # No stdlib constant exists for this value
-    DETACHED_PROCESS = 0x00000008
-    # Following creation flags might create a console GUI window.
-    # Using subprocess.CREATE_NEW_CONSOLE might helps.
-    # See https://phab.mercurial-scm.org/D1701 for discussion
-    _creationflags = (
-        DETACHED_PROCESS
-        | subprocess.CREATE_NEW_PROCESS_GROUP  # pytype: disable=module-attr
-    )
-
-    def runbgcommand(
-        script, env, shell=False, stdout=None, stderr=None, ensurestart=True
-    ):
-        '''Spawn a command without waiting for it to finish.'''
-        # we can't use close_fds *and* redirect stdin. I'm not sure that we
-        # need to because the detached process has no console connection.
-        subprocess.Popen(
-            tonativestr(script),
-            shell=shell,
-            env=tonativeenv(env),
-            close_fds=True,
-            creationflags=_creationflags,
-            stdout=stdout,
-            stderr=stderr,
-        )
-
-
-else:
-
-    def runbgcommand(
-        cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True
-    ):
-        '''Spawn a command without waiting for it to finish.'''
-        # double-fork to completely detach from the parent process
-        # based on http://code.activestate.com/recipes/278731
-        pid = os.fork()
-        if pid:
-            if not ensurestart:
-                return
-            # Parent process
-            (_pid, status) = os.waitpid(pid, 0)
-            if os.WIFEXITED(status):
-                returncode = os.WEXITSTATUS(status)
-            else:
-                returncode = -(os.WTERMSIG(status))
-            if returncode != 0:
-                # The child process's return code is 0 on success, an errno
-                # value on failure, or 255 if we don't have a valid errno
-                # value.
-                #
-                # (It would be slightly nicer to return the full exception info
-                # over a pipe as the subprocess module does.  For now it
-                # doesn't seem worth adding that complexity here, though.)
-                if returncode == 255:
-                    returncode = errno.EINVAL
-                raise OSError(
-                    returncode,
-                    b'error running %r: %s' % (cmd, os.strerror(returncode)),
-                )
-            return
-
-        returncode = 255
-        try:
-            # Start a new session
-            os.setsid()
-
-            stdin = open(os.devnull, b'r')
-            if stdout is None:
-                stdout = open(os.devnull, b'w')
-            if stderr is None:
-                stderr = open(os.devnull, b'w')
-
-            # connect stdin to devnull to make sure the subprocess can't
-            # muck up that stream for mercurial.
-            subprocess.Popen(
-                cmd,
-                shell=shell,
-                env=env,
-                close_fds=True,
-                stdin=stdin,
-                stdout=stdout,
-                stderr=stderr,
-            )
-            returncode = 0
-        except EnvironmentError as ex:
-            returncode = ex.errno & 0xFF
-            if returncode == 0:
-                # This shouldn't happen, but just in case make sure the
-                # return code is never 0 here.
-                returncode = 255
-        except Exception:
-            returncode = 255
-        finally:
-            # mission accomplished, this child needs to exit and not
-            # continue the hg process here.
-            os._exit(returncode)
diff --git a/mercurial/utils/procutil.py b/mercurial/utils/procutil.py
--- a/mercurial/utils/procutil.py
+++ b/mercurial/utils/procutil.py
@@ -11,7 +11,6 @@ 
 
 import contextlib
 import errno
-import imp
 import io
 import os
 import signal
@@ -32,6 +31,11 @@ 
     pycompat,
 )
 
+# Import like this to keep import-checker happy
+from ..utils import (
+    resourceutil,
+)
+
 osutil = policy.importmod('osutil')
 
 stderr = pycompat.stderr
@@ -254,19 +258,6 @@ 
     return pipefilter(s, cmd)
 
 
-def mainfrozen():
-    """return True if we are a frozen executable.
-
-    The code supports py2exe (most common, Windows only) and tools/freeze
-    (portable, not much used).
-    """
-    return (
-        pycompat.safehasattr(sys, "frozen")
-        or pycompat.safehasattr(sys, "importers")  # new py2exe
-        or imp.is_frozen("__main__")  # old py2exe
-    )  # tools/freeze
-
-
 _hgexecutable = None
 
 
@@ -280,7 +271,7 @@ 
         mainmod = sys.modules['__main__']
         if hg:
             _sethgexecutable(hg)
-        elif mainfrozen():
+        elif resourceutil.mainfrozen():
             if getattr(sys, 'frozen', None) == 'macosx_app':
                 # Env variable set by py2app
                 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
@@ -456,7 +447,7 @@ 
     to avoid things opening new shell windows like batch files, so we
     get either the python call or current executable.
     """
-    if mainfrozen():
+    if resourceutil.mainfrozen():
         if getattr(sys, 'frozen', None) == 'macosx_app':
             # Env variable set by py2app
             return [encoding.environ[b'EXECUTABLEPATH']]
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -54,6 +54,7 @@ 
 from .utils import (
     compression,
     procutil,
+    resourceutil,
     stringutil,
 )
 
@@ -1823,7 +1824,7 @@ 
 
 
 # the location of data files matching the source code
-if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
+if resourceutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
     # executable version (py2exe) doesn't support __file__
     datapath = os.path.dirname(pycompat.sysexecutable)
 else:
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -24,7 +24,7 @@ 
     util,
 )
 from .utils import (
-    procutil,
+    resourceutil,
     stringutil,
 )
 
@@ -786,7 +786,7 @@ 
     """
     if (
         not pycompat.isdarwin
-        or procutil.mainfrozen()
+        or resourceutil.mainfrozen()
         or not pycompat.sysexecutable
     ):
         return False
diff --git a/mercurial/hook.py b/mercurial/hook.py
--- a/mercurial/hook.py
+++ b/mercurial/hook.py
@@ -22,6 +22,7 @@ 
 )
 from .utils import (
     procutil,
+    resourceutil,
     stringutil,
 )
 
@@ -48,7 +49,7 @@ 
             )
         modname = funcname[:d]
         oldpaths = sys.path
-        if procutil.mainfrozen():
+        if resourceutil.mainfrozen():
             # binary installs require sys.path manipulation
             modpath, modfile = os.path.split(modname)
             if modpath and modfile: