Beispiel #1
0
def is_supported() -> bool:
    '''
    ``True`` only if the current platform is officially supported by
    this application.

    This function currently only returns ``True`` for the following platforms
    (in no particular order):

    * Apple macOS.
    * Linux.
    * Microsoft Windows.

    Caveats
    ----------
    This function returning ``True`` does *not* necessarily imply this
    application to behave optimally under this platform. In particular,
    Microsoft Windows is officially supported by popular demand but well-known
    to behave suboptimally with respect to Python itself and third-party
    scientific frameworks for Python (e.g., Numpy, Matplotlib).
    '''

    # Avoid circular import dependencies.
    from betse.util.os.brand import linux, macos, windows

    # The drawing of the Three draws nigh.
    return linux.is_linux() or macos.is_macos() or windows.is_windows()
Beispiel #2
0
def _is_blas_optimized_opt_info_macos() -> BoolOrNoneTypes:
    '''
    ``True`` only if the current platform is macOS *and* the
    ``extra_link_args`` list of the global
    :data:`numpy.__config__.blas_opt_info` dictionary both exists *and*
    heuristically corresponds to that of an optimized BLAS implementation
    specific to macOS (e.g., Accelerate, vecLib), ``False`` if a non-fatal error
    condition arises (e.g., due this list or dictionary being undefined), *or*
    ``None`` otherwise.

    This function returns ``None`` when unable to deterministically decide this
    boolean, in which case a subsequent heuristic will attempt to do so.

    Unlike all other BLAS implementations, macOS-specific BLAS implementations
    are linked against with explicit linker flags rather than pathnames. For
    further confirmation that the :attr:`numpy.__config__.blas_opt_info`
    dictionary defines these flags when linked to these implementations, see:

    * https://trac.macports.org/ticket/22200
    * https://github.com/BVLC/caffe/issues/2677

    When life buys you cat food, you eat cat food.
    '''

    # If the current platform is *NOT* macOS, continue to the next heuristic.
    if not macos.is_macos():
        return None
    # Else, the current platform is macOS.

    # List of all implementation-specific link arguments with which Numpy
    # linked against the current BLAS implementation if any or "None".
    #
    # Note that the "blas_opt_info" dictionary global is guaranteed to exist
    # due to the previously called _is_blas_optimized_opt_info_basename()
    # function.
    blas_link_args_list = numpy_config.blas_opt_info.get(
        'extra_link_args', None)

    # If no such argument exists, continue to the next heuristic. Since this
    # list is strictly optional, no errors or warnings are logged.
    if not blas_link_args_list:
        return None

    # Set of these arguments, converted from this list for efficiency.
    blas_link_args = set(blas_link_args_list)
    # logs.log_info('blas_link_args: {}'.format(blas_link_args))

    # Subset of this set specific to multithreaded BLAS implementations.
    blas_link_args_multithreaded = (
        blas_link_args & _OPTIMIZED_BLAS_OPT_INFO_EXTRA_LINK_ARGS_MACOS)

    # If this subset is nonempty, return True.
    if len(blas_link_args_multithreaded) > 0:
        return True

    # Else, instruct our caller to continue to the next heuristic.
    return None
Beispiel #3
0
def is_parent_command(command_basename: str) -> bool:
    '''
    ``True`` only if the parent process of the active Python interpreter is
    running an external command with the passed basename optionally suffixed by
    a platform-specific filetype (e.g., ``.exe`` under Windows).

    Parameters
    ----------
    command_basename : str
        Basename of the command to test this process' parent process against.

    Raises
    ------
    BetseOSException
        If the optional :mod:`psutil` dependency is unimportable *and* the
        current platform is neither Linux, macOS, or Windows.
    BetseFunctionUnimplementedException
        If the optional :mod:`psutil` dependency is unimportable.
    BetseProcessNotFoundException
        If either:

        * The optional :mod:`psutil` dependency is importable *and* either:

          * The current process has *no* parent process.
          * The current process had a parent process that has since died.

    Returns
    ----------
    bool
        ``True`` only if the parent process of the active Python interpreter is
        running an external command with the passed basename.
    '''

    # Avoid circular import dependencies.
    from betse.lib import libs
    from betse.util.os import oses
    from betse.util.os.brand import linux, macos, windows

    # If the optional "psutil" dependency is importable, this dependency
    # typically implements more efficient *AND* portable solutions than all
    # alternatives and is hence preferred. In this case, defer to "psutil".
    if libs.is_runtime_optional('psutil'):
        return _is_parent_command_psutil(command_basename)
    # Else if the current platform is Linux, defer to a Linux-specific tester.
    elif linux.is_linux():
        return _is_parent_command_linux(command_basename)
    # Else if the current platform is macOS, defer to a macOS-specific tester.
    elif macos.is_macos():
        return _is_parent_command_macos(command_basename)
    # Else if the current platform is Windows, defer to a Windows-specific
    # tester.
    elif windows.is_windows():
        return _is_parent_command_windows(command_basename)

    # Else, raise an exception.
    oses.die_if_unsupported()
Beispiel #4
0
def get_version() -> str:
    '''
    Human-readable ``.``-delimited version specifier string of the current
    platform.

    This function returns:

    * Under Linux, the version of the current Linux distribution reported by
      the :func:`betse.util.os.brand.linux.get_distro_version` function.
    * Under macOS, this installation's current major and minor version (e.g.,
      ``10.9``).
    * Under Windows, this installation's current major version (e.g., ``8``).
      Unfortunately, there appears to be no consistently reliable means of
      obtaining this system's current major *and* minor version (e.g.,
      ``8.1``).
    * Under all other platforms, the string returned by the
      :func:`platform.release` function.
    '''

    # Avoid circular import dependencies.
    from betse.util.os.brand import linux, macos

    # Version specifier to be returned.
    os_version = None

    # If Linux, defer to the "linux" submodule.
    if linux.is_linux():
        os_version = linux.get_distro_version()
    # If macOS *AND* the platform.mac_ver() function is available, return the
    # first item of the 3-tuple returned by this function (e.g., "10.9"). Since
    # platform.release() returns the low-level kernel version (e.g., "13.0.0"),
    # this version is ignored when feasible.
    elif macos.is_macos() and hasattr(platform, 'mac_ver'):
        # platform.mac_ver() returns a 3-tuple (release, versioninfo, machine)
        # of macOS-specific metadata, where "versioninfo" is itself a 3-tuple
        # (version, dev_stage, non_release_version). Return the high-level
        # "release" element (e.g., "10.9") rather than the optionally defined
        # low-level "version" element of the "versioninfo" element, which is
        # typically the empty string and hence useless.
        os_version = platform.mac_ver()[0]
    # If Windows, accept the default version specifier returned by
    # platform.release() (e.g., "8", "10"). Since this specifier appears to
    # coincide exactly with the first element of the 4-tuple returned by the
    # conditionally available platform.win32_ver() function, there is no
    # benefit to the latter approach.

    # If no platform-specific version specifier was detected above, default to
    # the version specifier returned by the platform.release() function. Since
    # this typically corresponds to the low-level version of the current kernel
    # (e.g., "4.1.15") rather than the high-level version of the current OS
    # (e.g., "6.4"), this is merely a fallback of last resort.
    if os_version is None:
        os_version = platform.release()

    # Return this version specifier.
    return os_version
Beispiel #5
0
    def finalize_options(self):
        '''
        Default undefined command-specific options to the options passed to the
        current parent command if any (e.g., ``install``).
        '''

        # Defer heavyweight imports.
        from betse.util.os.brand import macos
        from betse.util.path import dirs, pathnames
        from betse.util.os.command import cmdrun, cmds

        # Finalize superclass options.
        super().finalize_options()

        #FIXME: Replicate this functionality for the "install" command as well.

        # If the current system is OS X *AND* the OS X-specific Homebrew package
        # manager is installed...
        if macos.is_macos() and cmds.is_cmd('brew'):
            # Absolute dirname of Homebrew's top-level system-wide cellar
            # directory (e.g., "/usr/local/Cellar").
            brew_cellar_dir = cmdrun.get_output_interleaved_or_die(
                command_words=('brew', '--cellar'))
            #print('Here!')

            # Absolute dirname of Homebrew's top-level system-wide directory
            # (e.g., "/usr/local").
            brew_dir = cmdrun.get_output_interleaved_or_die(
                command_words=('brew', '--prefix'))

            # Absolute dirname of Homebrew's top-level system-wide binary
            # directory (e.g., "/usr/local/bin").
            brew_binary_dir = pathnames.join(brew_dir, 'bin')

            # If this directory does not exist, raise an exception.
            dirs.die_unless_dir(brew_binary_dir)

            # If the directory to which wrappers will be installed is a
            # Python-specific subdirectory of this cellar directory (e.g.,
            # "/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/bin"),
            # that subdirectory is unlikely to reside in the current ${PATH},
            # in which case wrappers installed to that subdirectory will remain
            # inaccessible. Correct this by forcing wrappers to be installed
            # to the Homebrew's conventional binary directory instead.
            if self.install_scripts.startswith(brew_cellar_dir):
                self.install_scripts = brew_binary_dir
                print('Detected Homebrew installation directory "{}".'.format(
                    brew_binary_dir))
Beispiel #6
0
def get_command_line_prefix() -> list:
    '''
    List of one or more shell words unambiguously running the executable binary
    for the active Python interpreter and machine architecture.

    Since the absolute path of the executable binary for the active Python
    interpreter is insufficient to unambiguously run this binary under the
    active machine architecture, this function should *always* be called in
    lieu of :func:`get_filename` when attempting to rerun this interpreter as a
    subprocess of the current Python process. As example:

    * Under macOS, the executable binary for this interpreter may be bundled
      with one or more other executable binaries targetting different machine
      architectures (e.g., 32-bit, 64-bit) in a single so-called "universal
      binary." Distinguishing between these bundled binaries requires passing
      this interpreter to a prefixing macOS-specific command, ``arch``.
    '''

    # Avoid circular import dependencies.
    from betse.util.os.brand import macos

    # List of such shell words.
    command_line = None

    # If this is OS X, this interpreter is only unambiguously runnable via the
    # OS X-specific "arch" command.
    if macos.is_macos():
        # Run the "arch" command.
        command_line = ['arch']

        # Instruct this command to run the architecture-specific binary in
        # Python's universal binary corresponding to the current architecture.
        if is_wordsize_64():
            command_line.append('-i386')
        else:
            command_line.append('-x86_64')

        # Instruct this command, lastly, to run this interpreter.
        command_line.append(get_filename())
    # Else, this interpreter is unambiguously runnable as is.
    else:
        command_line = [get_filename()]

    # Return this list.
    return command_line
Beispiel #7
0
def _is_headless() -> bool:
    '''
    ``True`` only if the active Python interpreter is running **headless**
    (i.e., with *no* access to a GUI display, often due to running remotely
    over an SSH-encrypted connection supporting only CLI input and output).

    Specifically, this function returns:

    * If the :func:`set_headless` function has been called at least once, the
      boolean passed to the most recent call to that function. Ergo, that
      function overrides the intelligent detection performed by this function.
    * Else, ``True`` only if none of the following conditions apply:

      * The current platform is Microsoft Windows. While a small subset of
        server-specific variants of Windows can and often are run headless
        (e.g., Windows Nano Server), there appears to be no known means of
        reliably distinguishing a headless from headfull Windows environment in
        pure Python. For safety, we assume the latter.
      * The current platform is not Microsoft Windows (and hence is
        POSIX-compatible) *and* either:

        * The ``${DISPLAY}`` environment variable required by the
          POSIX-compatible X11 display server is set under the current
          environment (typically due to being inherited from the parent
          environment). Note that *all* POSIX-compatible platforms of interest
          including macOS support this server.
        * The current platform is macOS *and* the :func:`macos.is_aqua` returns
          ``True``.
        * The current platform is Linux *and* either:

          * The ``${MIR_SOCKET}`` environment variable required by the
            Linux-specific Mir display server is set under this environment.
          * The ``${WAYLAND_DISPLAY}`` environment variable required by the
            Linux-specific Wayland display server is set under this
            environment.
    '''

    # Avoid circular import dependencies.
    from betse.util.os.brand import linux, macos, posix, windows

    # The active Python interpreter is headfull if and only if either...
    is_os_headfull = (
        # This is Windows, in which case this interpreter is usually headfull.
        # While certain server-specific variants of Windows can and often are
        # run headless (e.g., Windows Nano Server), there appears to be no
        # known means of reliably distinguishing a headless from headfull
        # Windows environment in pure Python. For safety, assume the latter.
        windows.is_windows() or

        # Else, this is a POSIX-compatible platform.
        #
        # Since all POSIX-compatible platforms of interest support the popular
        # X11 display server, detect this server first.
        posix.is_x11() or

        # Else, all possible alternative display servers specific to the
        # current platform *MUST* be iteratively tested for.
        #
        # If Linux, the only remaining display servers are Mir and Wayland.
        (linux.is_linux() and (linux.is_wayland() or linux.is_mir())) or

        # If macOS, the only remaining display server is Aqua.
        (macos.is_macos() and macos.is_aqua())

        # Else, this platform is unrecognized. For safety, this platform is
        # assumed to be headless.
    )

    # Return true only if this interpreter is *NOT* headfull.
    #
    # Note that the "is_os_headfull" boolean intentionally contains the core
    # detection logic, as detecting headfull environments is fundamentally
    # more intuitive than detecting the converse.
    return not is_os_headfull
Beispiel #8
0
    def dot_dirname(self) -> str:
        '''
        Absolute dirname of this application's **root dot directory** (i.e.,
        top-level directory containing this application's user-specific files
        and hence residing in the home directory of the current user), silently
        creating this directory if *not* already found.

        This directory contains user-specific files (e.g., logfiles, profile
        files) both read from and written to at application runtime. These are
        typically plaintext files consumable by external users and third-party
        utilities.

        Locations
        ----------
        Denote:

        * ``{package_name}`` the value of the :meth:`package_name` property for
          this application (e.g., ``betse`` for BETSE).
        * ``{username}`` the name of the current user (e.g., ``leycec``).

        Then the dirname returned by this property is:

        * Under Linux, ``~/.{package_name}/``. This property intentionally does
          *not* currently comply with the `XDG Base Directory Specification`_
          (e.g., ``~/.local/share/betse``), which the authors regard as
          `unhelpful if not blatantly harmful <xkcd Standards_>`__.
        * Under OS X, ``~/Library/Application Support/{package_name}``.
        * Under Windows,
          ``C:\\Documents and Settings\\{username}\\Application Data\\{package_name}``.

        .. _XDG Base Directory Specification:
            http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
        .. _xkcd Standards:
            https://xkcd.com/927
        '''

        # Avoid circular import dependencies.
        from betse.util.os.brand import macos, posix, windows
        from betse.util.os.shell import shellenv
        from betse.util.path import dirs, pathnames

        # Absolute dirname of this directory.
        dot_dirname = None

        # If macOS, prefer a macOS-specific directory.
        if macos.is_macos():
            dot_dirname = pathnames.join(
                pathnames.get_home_dirname(),
                'Library',
                'Application Support',
                self.package_name,
            )
        # If Windows, prefer a Windows-specific directory.
        elif windows.is_windows():
            dot_dirname = pathnames.join(
                shellenv.get_var('APPDATA'), self.package_name)
        # Else...
        else:
            # If this platform is POSIX-incompatible, raise an exception.
            posix.die_unless_posix()

            # Prefer a POSIX-compatible directory.
            dot_dirname = pathnames.join(
                pathnames.get_home_dirname(), '.' + self.package_name)

        # Create this directory if needed.
        dirs.make_unless_dir(dot_dirname)

        # Return this dirname.
        return dot_dirname
Beispiel #9
0
def get_name() -> str:
    '''
    Human-readable name of the current platform.

    This function returns:

    * Under Linux, the name of this Linux distribution, detected as follows:

      * If the :func:`platform.linux_distribution` function is available, the
        first element of the 3-tuple returned by this function (e.g.,
        ``CentOS``).
      * Else, the string returned by the :func:`platform.system` function.
        Since this is probably the generic string ``Linux``, this is only a
        fallback.

    * Under macOS, ``macOS``.
    * Under Windows, the flavour of the operating system-level kernel exposed
      by the shell environment to which the active Python interpreter is
      attached. Specifically, if this is:

      * The Windows Subsystem for Linux (WSL), ``Windows (WSL)``. Note,
        however, that the WSL masquerades as Linux. Thankfully, it does so to a
        remarkably more reliable degree than Cygwin Windows.
      * Cygwin Windows, ``Windows (Cygwin)``. Note, however, that Cygwin
        masquerades as Linux, replicating the POSIX process model and GNU/Linux
        userland to a remarkable degree -- subject to the Big List of Dodgy
        Apps (BLODA), proprietary limitations of the Win32 API, and similar
        caveats.
      * Under vanilla Windows shell environments (e.g., CMD.exe, PowerShell),
        merely ``Windows``.

    * Under all other platforms, the string returned by the
      :func:`platform.system` function.
    '''

    # Avoid circular import dependencies.
    from betse.util.os.brand import linux, macos, windows

    # Platform name to be returned.
    os_name = None

    # If Linux, defer to the "linux" submodule.
    if linux.is_linux():
        os_name = linux.get_distro_name()
    # If macOS, return "macOS". Since platform.system() returns the low-level
    # kernel name "Darwin", this name is ignored.
    elif macos.is_macos():
        os_name = 'macOS'
    # If Cygwin Windows, return "Windows (Cygwin)". Since platform.system()
    # returns a non-human-readable low-level uppercase label specific to the
    # word size of the current Python interpreter (e.g., "CYGWIN_NT-5.1*"),
    # this label is ignored.
    elif windows.is_windows_cygwin():
        os_name = 'Windows (Cygwin)'
    # If non-Cygwin Windows, return merely "Windows". The name returned by
    # platform.system() depends on the current Windows version as follows:
    #
    # * If this is Windows Vista, this name is "Microsoft".
    # * Else, this name is "Windows".
    #
    # Hence, this name is ignored.
    elif windows.is_windows_vanilla():
        os_name = 'Windows'
    # Else, reuse the name returned by the prior call to platform.system().

    # If no platform-specific name was detected above, default to the platform
    # name returned by the platform.system() function. Since this typically
    # corresponds to the low-level name of the current kernel (e.g., "Darwin",
    # "Linux") rather than the high-level name of the current platform (e.g.,
    # "macOS", "CentOS"), this is only a fallback of last resort.
    if os_name is None:
        os_name = platform.system()

    # Return the name established above.
    return os_name