Source code for extra_platforms.detection

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
"""Heuristics to detect platforms.

This collection of heuristics is designed as a set of separate function with minimal
logic and dependencies.

All these heuristics can be hard-cached as the underlying system is not changing
between code execution. They are still allowed to depends on each others, as long as
you're careful of not implementing circular dependencies.

.. warning::

    Even if highly unlikely, it is possible to have multiple platforms detected for the
    same environment.

    Typical example is `Ubuntu WSL <https://documentation.ubuntu.com/wsl/>`_, which
    will make both the ``is_wsl2()`` and ``is_ubuntu()`` functions return ``True`` at
    the same time.

    That's because of the environment metadata, where:

    .. code-block:: shell-session

        $ uname -a
        Linux 5.15.167.4-microsoft-standard-WSL2

        $ cat /etc/os-release
        PRETTY_NAME="Ubuntu 22.04.5 LTS"

    That way we have the possibility elsewhere in ``extra-platforms`` to either decide
    if we only allow one, and only one, heuristic to match the current system, or allow
    for considering multiple systems at the same time.

Detection of Linux distribution rely on `distro
<https://github.com/python-distro/distro>`_ to gather as much details as possible.
And also because it is the recommended replacement for Python's original
``platform.linux_distribution`` function (which was removed in Python 3.8).

For all other platforms, we either rely on:
- `sys.platform
  <https://docs.python.org/3/library/sys.html#sys.platform>`_
- `platform.platform
  <https://docs.python.org/3/library/platform.html#platform.platform>`_
- `platform.release
  <https://docs.python.org/3/library/platform.html#platform.release>`_
- environment variables

.. seealso::

    Other source of inspiration for platform detection:
    - `Rust's sysinfo crate
      <https://github.com/stanislav-tkach/os_info/tree/master/os_info/src>`_.
"""

from __future__ import annotations

import logging
import platform
import sys
from functools import cache
from os import environ

import distro

from . import _report_msg


[docs] @cache def is_aix() -> bool: """Return `True` if current platform is AIX.""" return sys.platform.startswith("aix") or distro.id() == "aix"
[docs] @cache def is_altlinux() -> bool: """Return `True` if current platform is ALT Linux.""" return distro.id() == "altlinux"
[docs] @cache def is_amzn() -> bool: """Return `True` if current platform is Amazon Linux.""" return distro.id() == "amzn"
[docs] @cache def is_android() -> bool: """Return `True` if current platform is Android. Source: https://github.com/kivy/kivy/blob/master/kivy/utils.py#L429 """ return "ANDROID_ROOT" in environ or "P4A_BOOTSTRAP" in environ
[docs] @cache def is_arch() -> bool: """Return `True` if current platform is Arch Linux.""" return distro.id() == "arch"
[docs] @cache def is_azure_pipelines() -> bool: """Return `True` if current platform is Azure Pipelines. `Environment variables reference <https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&viewFallbackFrom=vsts&tabs=yaml#system-variables>`_. """ return "TF_BUILD" in environ
[docs] @cache def is_bamboo() -> bool: """Return `True` if current platform is Bamboo. `Environment variables reference <https://confluence.atlassian.com/bamboo/bamboo-variables-289277087.html#Bamboovariables-Build-specificvariables>`_. """ return "bamboo.buildKey" in environ
[docs] @cache def is_buildkite() -> bool: """Return `True` if current platform is Buildkite. `Environment variables reference <https://buildkite.com/docs/pipelines/environment-variables>`_. """ return "BUILDKITE" in environ
[docs] @cache def is_buildroot() -> bool: """Return `True` if current platform is Buildroot.""" return distro.id() == "buildroot"
[docs] @cache def is_centos() -> bool: """Return `True` if current platform is CentOS.""" return distro.id() == "centos"
[docs] @cache def is_circle_ci() -> bool: """Return `True` if current platform is Circle CI. `Environment variables reference <https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables>`_. """ return "CIRCLECI" in environ
[docs] @cache def is_cirrus_ci() -> bool: """Return `True` if current platform is Cirrus CI. `Environment variables reference <https://cirrus-ci.org/guide/writing-tasks/#environment-variables>`_. """ return "CIRRUS_CI" in environ
[docs] @cache def is_cloudlinux() -> bool: """Return `True` if current platform is CloudLinux OS.""" return distro.id() == "cloudlinux"
[docs] @cache def is_codebuild() -> bool: """Return `True` if current platform is CodeBuild. `Environment variables reference <https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html>`_. """ return "CODEBUILD_BUILD_ID" in environ
[docs] @cache def is_cygwin() -> bool: """Return `True` if current platform is Cygwin.""" return sys.platform.startswith("cygwin")
[docs] @cache def is_debian() -> bool: """Return `True` if current platform is Debian.""" return distro.id() == "debian"
[docs] @cache def is_exherbo() -> bool: """Return `True` if current platform is Exherbo Linux.""" return distro.id() == "exherbo"
[docs] @cache def is_fedora() -> bool: """Return `True` if current platform is Fedora.""" return distro.id() == "fedora"
[docs] @cache def is_freebsd() -> bool: """Return `True` if current platform is FreeBSD.""" return sys.platform.startswith("freebsd") or distro.id() == "freebsd"
[docs] @cache def is_gentoo() -> bool: """Return `True` if current platform is GenToo Linux.""" return distro.id() == "gentoo"
[docs] @cache def is_github_ci() -> bool: """Return `True` if current platform is GitHub Actions runner. `Environment variables reference <https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables>`_. """ return "GITHUB_ACTIONS" in environ or "GITHUB_RUN_ID" in environ
[docs] @cache def is_gitlab_ci() -> bool: """Return `True` if current platform is GitLab CI. `Environment variables reference https://docs.gitlab.com/ci/variables/predefined_variables/#predefined-variables>`_. """ return "GITLAB_CI" in environ
[docs] @cache def is_guix() -> bool: """Return `True` if current platform is Guix System.""" return distro.id() == "guix"
[docs] @cache def is_heroku_ci() -> bool: """Return `True` if current platform is Heroku CI. `Environment variables reference <https://devcenter.heroku.com/articles/heroku-ci#immutable-environment-variables>`_. """ return "HEROKU_TEST_RUN_ID" in environ
[docs] @cache def is_hurd() -> bool: """Return `True` if current platform is GNU/Hurd.""" return sys.platform.startswith("GNU")
[docs] @cache def is_ibm_powerkvm() -> bool: """Return `True` if current platform is IBM PowerKVM.""" return distro.id() == "ibm_powerkvm"
[docs] @cache def is_kvmibm() -> bool: """Return `True` if current platform is KVM for IBM z Systems.""" return distro.id() == "kvmibm"
[docs] @cache def is_linuxmint() -> bool: """Return `True` if current platform is Linux Mint.""" return distro.id() == "linuxmint"
[docs] @cache def is_macos() -> bool: """Return `True` if current platform is macOS.""" return platform.platform(terse=True).startswith(("macOS", "Darwin"))
[docs] @cache def is_mageia() -> bool: """Return `True` if current platform is Mageia.""" return distro.id() == "mageia"
[docs] @cache def is_mandriva() -> bool: """Return `True` if current platform is Mandriva Linux.""" return distro.id() == "mandriva"
[docs] @cache def is_midnightbsd() -> bool: """Return `True` if current platform is MidnightBSD.""" return sys.platform.startswith("midnightbsd") or distro.id() == "midnightbsd"
[docs] @cache def is_netbsd() -> bool: """Return `True` if current platform is NetBSD.""" return sys.platform.startswith("netbsd") or distro.id() == "netbsd"
[docs] @cache def is_nobara() -> bool: """Return `True` if current platform is Nobara Linux.""" return distro.id() == "nobara"
[docs] @cache def is_openbsd() -> bool: """Return `True` if current platform is OpenBSD.""" return sys.platform.startswith("openbsd") or distro.id() == "openbsd"
[docs] @cache def is_opensuse() -> bool: """Return `True` if current platform is openSUSE.""" return distro.id() == "opensuse"
[docs] @cache def is_oracle() -> bool: """Return `True` if current platform is Oracle Linux (and Oracle Enterprise Linux).""" return distro.id() == "oracle"
[docs] @cache def is_parallels() -> bool: """Return `True` if current platform is Parallels.""" return distro.id() == "parallels"
[docs] @cache def is_pidora() -> bool: """Return `True` if current platform is Pidora.""" return distro.id() == "pidora"
[docs] @cache def is_raspbian() -> bool: """Return `True` if current platform is Raspbian.""" return distro.id() == "raspbian"
[docs] @cache def is_rhel() -> bool: """Return `True` if current platform is RedHat Enterprise Linux.""" return distro.id() == "rhel"
[docs] @cache def is_rocky() -> bool: """Return `True` if current platform is Rocky Linux.""" return distro.id() == "rocky"
[docs] @cache def is_scientific() -> bool: """Return `True` if current platform is Scientific Linux.""" return distro.id() == "scientific"
[docs] @cache def is_slackware() -> bool: """Return `True` if current platform is Slackware.""" return distro.id() == "slackware"
[docs] @cache def is_sles() -> bool: """Return `True` if current platform is SUSE Linux Enterprise Server.""" return distro.id() == "sles"
[docs] @cache def is_solaris() -> bool: """Return `True` if current platform is Solaris.""" return platform.platform(aliased=True, terse=True).startswith("Solaris")
[docs] @cache def is_sunos() -> bool: """Return `True` if current platform is SunOS.""" return platform.platform(aliased=True, terse=True).startswith("SunOS")
[docs] @cache def is_teamcity() -> bool: """Return `True` if current platform is TeamCity. `Environment variables reference <https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#PredefinedBuildParameters-ServerBuildProperties>`_. """ return "TEAMCITY_VERSION" in environ
[docs] @cache def is_travis_ci() -> bool: """Return `True` if current platform is Travis CI. `Environment variables reference <https://docs.travis-ci.com/user/environment-variables/#default-environment-variables>`_. """ return "TRAVIS" in environ
[docs] @cache def is_tumbleweed() -> bool: """Return `True` if current platform is openSUSE Tumbleweed.""" return distro.id() == "opensuse-tumbleweed"
[docs] @cache def is_tuxedo() -> bool: """Return `True` if current platform is Tuxedo OS.""" return distro.id() == "tuxedo"
[docs] @cache def is_ubuntu() -> bool: """Return `True` if current platform is Ubuntu.""" return distro.id() == "ubuntu"
[docs] @cache def is_unknown_ci() -> bool: """Return `True` if current platform is an unknown CI. Some CI systems relies on `generic environment variables to identify themselves <https://adamj.eu/tech/2020/03/09/detect-if-your-tests-are-running-on-ci/>`_: - `CI` - `BUILD_ID` """ if any(( is_azure_pipelines(), is_bamboo(), is_buildkite(), is_circle_ci(), is_cirrus_ci(), is_codebuild(), is_github_ci(), is_gitlab_ci(), is_heroku_ci(), is_teamcity(), is_travis_ci(), )): return False if "CI" in environ or "BUILD_ID" in environ: logging.warning(f"Unknown CI detected: {environ}. {_report_msg}") return True return False
[docs] @cache def is_unknown_linux() -> bool: """Return `True` if current platform is an unknown Linux.""" if any(( is_altlinux(), is_amzn(), is_android(), is_arch(), is_buildroot(), is_centos(), is_cloudlinux(), is_debian(), is_exherbo(), is_fedora(), is_gentoo(), is_guix(), is_ibm_powerkvm(), is_kvmibm(), is_linuxmint(), is_mageia(), is_mandriva(), is_nobara(), is_opensuse(), is_oracle(), is_parallels(), is_pidora(), is_raspbian(), is_rhel(), is_rocky(), is_scientific(), is_slackware(), is_sles(), is_tumbleweed(), is_tuxedo(), is_ubuntu(), is_xenserver(), )): return False if sys.platform.startswith("linux"): logging.warning(f"Unknown Linux detected: {distro.info()!r}. {_report_msg}") return True return False # type: ignore[unreachable,unused-ignore]
[docs] @cache def is_windows() -> bool: """Return `True` if current platform is Windows.""" return sys.platform.startswith("win32")
[docs] @cache def is_wsl1() -> bool: """Return `True` if current platform is running over Windows Subsystem for Linux v1. .. caution:: The only difference between WSL1 and WSL2 is `the case of the kernel release version <https://github.com/andweeb/presence.nvim/pull/64#issue-1174430662>`_: - WSL 1: .. code-block:: shell-session $ uname -r 4.4.0-22572-Microsoft - WSL 2: .. code-block:: shell-session $ uname -r 5.10.102.1-microsoft-standard-WSL2 """ return "Microsoft" in platform.release()
[docs] @cache def is_wsl2() -> bool: """Return `True` if current platform is running over Windows Subsystem for Linux v2.""" return "microsoft" in platform.release()
[docs] @cache def is_xenserver() -> bool: """Return `True` if current platform is XenServer.""" return distro.id() == "xenserver"