Пример #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()
Пример #2
0
def is_windows_wsl() -> bool:
    '''
    ``True`` only if the current platform is **Windows Subsystem for Linux
    (WSL)** (i.e., the Microsoft-flavoured Linux kernel optionally supported by
    Windows 10).

    See Also
    ----------
    https://www.reddit.com/r/bashonubuntuonwindows/comments/85jghk/how_to_allow_python_to_know_im_on_windows/dvxwy9t
        Reddit post strongly inspiring this implementation.
    '''

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

    # If the active Python interpreter is *NOT* operating under a Linux kernel,
    # return false immediately.
    if not linux.is_linux():
        return False
    # Else, this interpreter is operating under a Linux kernel.

    # Flavour of this Linux kernel.
    kernel_flavour = platform.uname()[3]

    # Return true only if this is a Microsoft-flavoured Linux kernel.
    return 'microsoft' in kernel_flavour
Пример #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()
Пример #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
Пример #5
0
def iter_linked_filenames(filename: str) -> GeneratorType:
    '''
    Generator iteratively yielding the 2-tuple of the basename and absolute
    filename of each shared library dynamically linked to (and hence required
    at runtime by) the shared library with the passed filename.

    Parameters
    ----------
    filename : str
        Absolute or relative filename of the shared library to inspect.

    Yields
    ----------
    (str, str)
        2-tuple ``(linked_lib_basename, linked_lib_pathname``) such that:

        * ``linked_lib_basename`` is the basename of a shared library
          dynamically linked to the shared library with the passed path.
        * ``linked_lib_pathname`` is the absolute pathname of the same library.
    '''

    # Avoid circular import dependencies.
    from betse.util.os import oses
    from betse.util.os.brand import linux
    from betse.util.os.command import cmdrun
    from betse.util.type.text import regexes

    # If this library does *NOT* exist, raise an exception.
    die_unless_dll(filename)

    # If the current platform is Linux...
    if linux.is_linux():
        # String listing all libraries linked to by this library captured from
        # stdout printed by this command. For example, when passed the absolute
        # path of the file defining the "numpy.core.multiarray" C extension,
        # this command prints stdout resembling:
        #
        # multiarray.cpython-34.so:
        # 	linux-vdso.so.1 (0x00007fff049ca000)
        # 	libopenblas_threads.so.0 => /usr/lib64/libopenblas_threads.so.0 (0x00007f9af3ad5000)
        # 	libm.so.6 => /lib64/libm.so.6 (0x00007f9af37d9000)
        # 	libpython3.4.so.1.0 => /usr/lib64/libpython3.4.so.1.0 (0x00007f9af3357000)
        # 	libc.so.6 => /lib64/libc.so.6 (0x00007f9af2fc1000)
        # 	/lib64/ld-linux-x86-64.so.2 (0x000055e2dd7f3000)
        # 	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f9af2da5000)
        # 	libdl.so.2 => /lib64/libdl.so.2 (0x00007f9af2ba0000)
        # 	libutil.so.1 => /lib64/libutil.so.1 (0x00007f9af299d000)
        ldd_stdout = cmdrun.get_stdout_or_die(command_words=('ldd', filename))

        # For each line containing a "=>"-delimited basename and absolute path
        # pair and hence ignoring both pseudo-libraries that do *NOT* actually
        # exist (e.g., "linux-vdso") or ubiquitous libraries required by the
        # dynamic linking mechanism and hence guaranteed to *ALWAYS* exist
        # (e.g., "ld-linux-x86-64")...
        for line_match in regexes.iter_matches_line(
                text=ldd_stdout,
                regex=r'^\s+(\S+)\s+=>\s+(\S+)\s+\(0x[0-9a-fA-F]+\)$',
        ):
            # Yield the 2-tuple corresponding exactly to the match groups
            # captured by this match.
            yield line_match.groups()

    # Else, library inspection is currently unsupported on this platform.
    raise BetseOSException(
        'Shared library inspection currently unsupported under {}.'.format(
            oses.get_name()))
Пример #6
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
Пример #7
0
def _is_blas_optimized_posix_symlink() -> BoolOrNoneTypes:
    '''
    ``True`` only if the current platform is POSIX-compliant and hence
    supports symbolic links *and* the first item of the ``libraries`` list of
    the global :data:`numpy.__config__.blas_opt_info` dictionary is a symbolic
    link masquerading as either the unoptimized reference BLAS implementation
    but in fact linking to an optimized BLAS implementation.

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

    # If the current platform is POSIX-incompatible and hence does *NOT*
    # support symbolic links, continue to the next heuristic.
    if not posix.is_posix():
        return None

    #FIXME: Generalize to macOS as well once the
    #libs.iter_linked_filenames() function supports macOS.

    # If the current platform is *NOT* Linux, continue to the next heuristic.
    #
    # The libs.iter_linked_filenames() function called below currently only
    # supports Linux.
    if not linux.is_linux():
        return None

    # First element of the list of uniquely identifying substrings of all BLAS
    # library basenames this version of Numpy is linked against.
    #
    # Note that this list is guaranteed to both exist and be non-empty due to
    # the previously called _is_blas_optimized_opt_info_basename() function.
    blas_basename_substr = numpy_config.blas_opt_info['libraries'][0]

    # If this element appears to be neither the reference BLAS or CBLAS
    # implementations (e.g., "blas", "cblas", "refblas", "refcblas"), continue
    # to the next heuristic.
    if not blas_basename_substr.endswith('blas'):
        return None

    # Arbitrary Numpy C extension.
    #
    # Unfortunately, the "numpy.__config__" API fails to specify the absolute
    # paths of the libraries it links against. Since there exists no reliable
    # means of reverse engineering these paths from this API, these paths must
    # be obtained by another means: specifically, by querying the standard
    # "numpy.core.multiarray" C extension installed under all supported Numpy
    # for the absolute paths of all external shared libraries to which this
    # extension links -- exactly one of which is guaranteed to be the absolute
    # path of what appears to be a reference BLAS or CBLAS implementation.
    numpy_lib = get_c_extension()

    # Absolute filename of this C extension.
    numpy_lib_filename = pymodule.get_filename(module=numpy_lib)

    # For the basename and absolute filename of each shared library linked to
    # by this Numpy shared library...
    for (numpy_linked_lib_basename, numpy_linked_lib_filename) in (
            dlls.iter_linked_filenames(numpy_lib_filename)):
        # Basename excluding all suffixing filetypes of this library.
        numpy_linked_lib_rootname = pathnames.get_pathname_sans_filetypes(
            numpy_linked_lib_basename)
        # logs.log_info('rootname: %s; basename: %s; filename: %s', numpy_linked_lib_rootname, numpy_linked_lib_basename, numpy_linked_lib_filename)

        # If this appears to be neither the BLAS nor CBLAS reference library,
        # continue to the next library.
        if not numpy_linked_lib_rootname.endswith('blas'):
            continue
        # Else, this is either the BLAS or CBLAS reference library.

        # Absolute filename of the target library to which this library links
        # if this library is a symbolic link *OR* of this library as is
        # otherwise (i.e., if this is a library rather than symbolic link).
        numpy_linked_lib_target_filename = pathnames.canonicalize(
            numpy_linked_lib_filename)
        # logs.log_info('target filename: %s', numpy_linked_lib_target_filename)

        # If either the basename or dirname of this path corresponds to that of
        # an optimized BLAS library, return True.
        if regexes.is_match(
                text=pathnames.get_basename(numpy_linked_lib_target_filename),
                regex=_OPTIMIZED_BLAS_LINKED_LIB_BASENAME_REGEX,
        ) or regexes.is_match(
                text=pathnames.get_dirname(numpy_linked_lib_target_filename),
                regex=_OPTIMIZED_BLAS_LINKED_LIB_DIRNAME_REGEX,
        ):
            return True

        # Else, Numpy links against an unoptimized BLAS implementation. Halt!
        break

    # Else, instruct our caller to continue to the next heuristic.
    return None
Пример #8
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