mirror of
https://github.com/nvm-sh/nvm.git
synced 2026-10-24 15:23:42 +08:00
[Perf] nvm_get_os: read /etc/os-release instead of forking uname -a
The file is read, not sourced. An `ID` with no OS of its own is only taken as Linux when the kernel's `ostype` says so; otherwise `uname -a` decides.
This commit is contained in:
committed by
Jordan Harband
parent
c28f2b70db
commit
1dd1d8c82e
78
nvm.sh
78
nvm.sh
@@ -2563,18 +2563,74 @@ nvm_print_implicit_alias() {
|
||||
}
|
||||
|
||||
nvm_get_os() {
|
||||
local NVM_UNAME
|
||||
NVM_UNAME="$(command uname -a)"
|
||||
# dash's `local` keeps the caller's value, so these must start empty
|
||||
local NVM_OS
|
||||
case "${NVM_UNAME}" in
|
||||
Linux\ *) NVM_OS=linux ;;
|
||||
Darwin\ *) NVM_OS=darwin ;;
|
||||
SunOS\ *) NVM_OS=sunos ;;
|
||||
FreeBSD\ *) NVM_OS=freebsd ;;
|
||||
OpenBSD\ *) NVM_OS=openbsd ;;
|
||||
AIX\ *) NVM_OS=aix ;;
|
||||
CYGWIN* | MSYS* | MINGW*) NVM_OS=win ;;
|
||||
esac
|
||||
NVM_OS=''
|
||||
local NVM_UNAME
|
||||
local NVM_OS_RELEASE_PATH
|
||||
local NVM_OS_RELEASE_LINE
|
||||
local NVM_OS_RELEASE_ID
|
||||
NVM_OS_RELEASE_ID=''
|
||||
local NVM_OS_KERNEL_PATH
|
||||
local NVM_OS_KERNEL_TYPE
|
||||
NVM_OS_KERNEL_TYPE=''
|
||||
|
||||
# `/etc/os-release` is available on Linux, FreeBSD 13.2+, and the MSYS2 and
|
||||
# Cygwin runtimes; where it is readable, its `ID` names the OS without
|
||||
# spawning `uname`. The file is read, not sourced, so nothing in it runs.
|
||||
# systems without the file (macOS, OpenBSD, ...) and files without an `ID`
|
||||
# fall through to `uname -a` below.
|
||||
NVM_OS_RELEASE_PATH="${NVM_OS_RELEASE:-/etc/os-release}"
|
||||
if [ -r "${NVM_OS_RELEASE_PATH}" ]; then
|
||||
while IFS= read -r NVM_OS_RELEASE_LINE || [ -n "${NVM_OS_RELEASE_LINE}" ]; do
|
||||
case "${NVM_OS_RELEASE_LINE}" in
|
||||
ID=*)
|
||||
NVM_OS_RELEASE_ID="${NVM_OS_RELEASE_LINE#ID=}"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done < "${NVM_OS_RELEASE_PATH}"
|
||||
# an `ID` is a single word, which may be quoted and followed by
|
||||
# whitespace (including a CR) or a comment
|
||||
NVM_OS_RELEASE_ID="${NVM_OS_RELEASE_ID%%[[:space:]#]*}"
|
||||
NVM_OS_RELEASE_ID="${NVM_OS_RELEASE_ID#[\"\']}"
|
||||
NVM_OS_RELEASE_ID="${NVM_OS_RELEASE_ID%[\"\']}"
|
||||
case "${NVM_OS_RELEASE_ID}" in
|
||||
freebsd) NVM_OS=freebsd ;;
|
||||
cygwin* | mingw* | msys*) NVM_OS=win ;;
|
||||
# illumos distributions and Oracle Solaris report `SunOS` from
|
||||
# `uname -a`, and GhostBSD reports `FreeBSD`; map these directly instead
|
||||
# of forking `uname`
|
||||
solaris | omnios | openindiana | smartos | illumos) NVM_OS=sunos ;;
|
||||
ghostbsd) NVM_OS=freebsd ;;
|
||||
# no `ID`, or one with no `nvm` OS of its own (DragonFly BSD): let
|
||||
# `uname -a` decide
|
||||
'' | dragonfly) NVM_OS='' ;;
|
||||
# any other `ID` is a Linux distribution's only if the kernel says so;
|
||||
# unlisted BSD and illumos derivatives (e.g. HardenedBSD, Helios) let
|
||||
# `uname -a` decide
|
||||
*)
|
||||
NVM_OS_KERNEL_PATH="${NVM_OS_KERNEL_OSTYPE:-/proc/sys/kernel/ostype}"
|
||||
if [ -r "${NVM_OS_KERNEL_PATH}" ] && IFS= read -r NVM_OS_KERNEL_TYPE < "${NVM_OS_KERNEL_PATH}" && [ "${NVM_OS_KERNEL_TYPE}" = 'Linux' ]; then
|
||||
NVM_OS=linux
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -z "${NVM_OS-}" ]; then
|
||||
NVM_UNAME="$(command uname -a)"
|
||||
case "${NVM_UNAME}" in
|
||||
Linux\ *) NVM_OS=linux ;;
|
||||
Darwin\ *) NVM_OS=darwin ;;
|
||||
SunOS\ *) NVM_OS=sunos ;;
|
||||
FreeBSD\ *) NVM_OS=freebsd ;;
|
||||
OpenBSD\ *) NVM_OS=openbsd ;;
|
||||
AIX\ *) NVM_OS=aix ;;
|
||||
CYGWIN* | MSYS* | MINGW*) NVM_OS=win ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
nvm_echo "${NVM_OS-}"
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ MOCKS_DIR="$(pwd)/../../mocks"
|
||||
# binaries
|
||||
export PATH=".:${PATH}"
|
||||
|
||||
# `nvm_get_os` reads `/etc/os-release` where it is available; this test drives
|
||||
# the OS through the mocked `uname`, so point it at a file that does not exist
|
||||
export NVM_OS_RELEASE="${MOCKS_DIR}/nonexistent-os-release"
|
||||
|
||||
# Setups mock binaries for a given OS and arch that mimic
|
||||
# the output of the real binaries used by nvm_get_arch to guess
|
||||
# the architecture of a given system.
|
||||
|
||||
@@ -15,6 +15,10 @@ die () { cleanup; echo "$@" ; exit 1; }
|
||||
MOCKS_DIR="$(pwd)/../../mocks"
|
||||
export PATH=".:${PATH}"
|
||||
|
||||
# `nvm_get_os` reads `/etc/os-release` where it is available; this test drives
|
||||
# the OS through the mocked `uname`, so point it at a file that does not exist
|
||||
export NVM_OS_RELEASE="${MOCKS_DIR}/nonexistent-os-release"
|
||||
|
||||
# On Alpine (where /etc/alpine-release exists), both x64 and arm64 should get
|
||||
# the -musl suffix, since unofficial-builds publishes musl binaries for both.
|
||||
# On non-Alpine, neither should get -musl.
|
||||
|
||||
149
test/fast/Unit tests/nvm_get_os
Executable file
149
test/fast/Unit tests/nvm_get_os
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/bin/sh
|
||||
|
||||
TEST_PWD=$(pwd)
|
||||
TEST_DIR="${TEST_PWD}/nvm_get_os_tmp"
|
||||
|
||||
: nvm.sh
|
||||
\. ../../../nvm.sh
|
||||
|
||||
cleanup() {
|
||||
rm -rf "${TEST_DIR}"
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "$@"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ ! -e "${TEST_DIR}" ] && mkdir "${TEST_DIR}"
|
||||
|
||||
OS_RELEASE="${TEST_DIR}/os-release"
|
||||
MISSING_OS_RELEASE="${TEST_DIR}/nonexistent-os-release"
|
||||
|
||||
# a `uname` that reports that it ran (and fails), so that any test can prove
|
||||
# whether `nvm_get_os` forked it
|
||||
MOCK_BIN="${TEST_DIR}/bin"
|
||||
mkdir -p "${MOCK_BIN}"
|
||||
{
|
||||
echo '#!/bin/sh'
|
||||
echo 'echo "uname was called" >&2'
|
||||
echo 'exit 1'
|
||||
} > "${MOCK_BIN}/uname"
|
||||
chmod +x "${MOCK_BIN}/uname"
|
||||
|
||||
# asserts that `nvm_get_os` maps the os-release file to $EXPECTED, without
|
||||
# forking `uname`; the kernel is mocked as Linux, so Linux distro `ID`s pass on any host
|
||||
KERNEL_LINUX="${TEST_DIR}/ostype-linux"
|
||||
echo 'Linux' > "${KERNEL_LINUX}"
|
||||
assert_os_release() {
|
||||
local DESCRIPTION
|
||||
DESCRIPTION="${1}"
|
||||
local EXPECTED
|
||||
EXPECTED="${2}"
|
||||
local OUTPUT
|
||||
NVM_OS_RELEASE="${OS_RELEASE}"
|
||||
NVM_OS_KERNEL_OSTYPE="${KERNEL_LINUX}"
|
||||
export NVM_OS_RELEASE NVM_OS_KERNEL_OSTYPE
|
||||
OUTPUT="$(PATH="${MOCK_BIN}:${PATH}" nvm_get_os 2>&1)"
|
||||
unset NVM_OS_RELEASE NVM_OS_KERNEL_OSTYPE
|
||||
case "${OUTPUT}" in
|
||||
*'uname was called'*) die "for ${DESCRIPTION}, \`nvm_get_os\` forked \`uname\` despite \`${OS_RELEASE}\` being readable" ;;
|
||||
esac
|
||||
[ "${OUTPUT}" = "${EXPECTED}" ] || die "for ${DESCRIPTION}, expected '${EXPECTED}', got '${OUTPUT}'"
|
||||
}
|
||||
|
||||
printf 'NAME=Ubuntu\nID=ubuntu\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'a distro-style os-release' 'linux'
|
||||
|
||||
printf 'NAME="Arch Linux"\nID=arch\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'an os-release with a quoted NAME' 'linux'
|
||||
|
||||
printf 'NAME=FreeBSD\nVERSION=14.0-RELEASE\nID=freebsd\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'the FreeBSD 13.2+ os-release' 'freebsd'
|
||||
|
||||
printf 'ID=freebsd' > "${OS_RELEASE}"
|
||||
assert_os_release 'an os-release whose ID has no trailing newline' 'freebsd'
|
||||
|
||||
printf 'NAME=MSYS2\nID=msys2\nPRETTY_NAME="MSYS2"\nID_LIKE="cygwin arch"\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'the MSYS2 os-release' 'win'
|
||||
|
||||
printf 'NAME="Oracle Solaris"\nPRETTY_NAME="Oracle Solaris 11.4"\nCPE_NAME="cpe:/o:oracle:solaris:11:4"\nID=solaris\nVERSION=11.4\nVERSION_ID=11.4\nBUILD_ID=11.4.0.0.1.15.0\nHOME_URL="https://www.oracle.com/solaris/"\nSUPPORT_URL="https://support.oracle.com/"\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'the Oracle Solaris 11.4 os-release' 'sunos'
|
||||
|
||||
printf 'NAME="OmniOS"\nPRETTY_NAME="OmniOS Community Edition v11 r151048"\nCPE_NAME="cpe:/o:omniosce:omnios:11:151048:0"\nID=omnios\nVERSION=r151048\nVERSION_ID=r151048\nBUILD_ID=151048.0.2023.11.04\nHOME_URL="https://omnios.org/"\nSUPPORT_URL="https://omnios.org/"\nBUG_REPORT_URL="https://github.com/omniosorg/omnios-build/issues/new"\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'the OmniOS r151048 os-release' 'sunos'
|
||||
|
||||
printf 'ID=openindiana\nID_LIKE="illumos solaris"\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'an OpenIndiana os-release' 'sunos'
|
||||
|
||||
printf 'ID=smartos\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'a SmartOS os-release' 'sunos'
|
||||
|
||||
printf 'ID=illumos\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'an illumos os-release' 'sunos'
|
||||
|
||||
printf 'ID=ghostbsd\nID_LIKE=freebsd\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'a GhostBSD os-release' 'freebsd'
|
||||
|
||||
printf 'ID=freebsd # a comment\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'an ID followed by whitespace and a comment' 'freebsd'
|
||||
|
||||
# the file is read, not sourced: nothing in it runs
|
||||
printf 'exit 3\nID=ubuntu\n' > "${OS_RELEASE}"
|
||||
assert_os_release 'an os-release containing shell code' 'linux'
|
||||
|
||||
# a file without an `ID`, or no file at all, falls back to parsing `uname -a`
|
||||
{
|
||||
echo '#!/bin/sh'
|
||||
echo 'echo "${NVM_MOCK_UNAME}"'
|
||||
} > "${MOCK_BIN}/uname"
|
||||
chmod +x "${MOCK_BIN}/uname"
|
||||
|
||||
assert_uname_os() {
|
||||
local UNAME_OUTPUT
|
||||
UNAME_OUTPUT="${1}"
|
||||
local OS_RELEASE_FILE
|
||||
OS_RELEASE_FILE="${2}"
|
||||
local EXPECTED
|
||||
EXPECTED="${3}"
|
||||
local OUTPUT
|
||||
NVM_OS_RELEASE="${OS_RELEASE_FILE}"
|
||||
NVM_OS_KERNEL_OSTYPE="${MISSING_OS_RELEASE}"
|
||||
export NVM_OS_RELEASE NVM_OS_KERNEL_OSTYPE
|
||||
NVM_MOCK_UNAME="${UNAME_OUTPUT}"
|
||||
export NVM_MOCK_UNAME
|
||||
OUTPUT="$(PATH="${MOCK_BIN}:${PATH}" nvm_get_os)"
|
||||
unset NVM_OS_RELEASE NVM_OS_KERNEL_OSTYPE NVM_MOCK_UNAME
|
||||
[ "${OUTPUT}" = "${EXPECTED}" ] || die "for uname -a '${UNAME_OUTPUT}', expected '${EXPECTED}', got '${OUTPUT}'"
|
||||
}
|
||||
|
||||
printf 'NAME=OnlyAName\n' > "${OS_RELEASE}"
|
||||
assert_uname_os 'Linux host 6.1.0 #1 SMP x86_64 GNU/Linux' "${OS_RELEASE}" 'linux'
|
||||
assert_uname_os 'Darwin host 23.1.0 arm64' "${OS_RELEASE}" 'darwin'
|
||||
assert_uname_os 'SunOS host 5.11' "${OS_RELEASE}" 'sunos'
|
||||
assert_uname_os 'FreeBSD host 14.0-RELEASE' "${OS_RELEASE}" 'freebsd'
|
||||
assert_uname_os 'OpenBSD host 7.4' "${OS_RELEASE}" 'openbsd'
|
||||
assert_uname_os 'AIX host 3 7' "${OS_RELEASE}" 'aix'
|
||||
assert_uname_os 'CYGWIN_NT-10.0 host' "${OS_RELEASE}" 'win'
|
||||
assert_uname_os 'MINGW64_NT-10.0 host' "${OS_RELEASE}" 'win'
|
||||
assert_uname_os 'MSYS_NT-10.0 host' "${OS_RELEASE}" 'win'
|
||||
assert_uname_os 'Darwin host 23.1.0 arm64' "${MISSING_OS_RELEASE}" 'darwin'
|
||||
|
||||
# a pre-set `ID` in the environment must not be mistaken for the file's `ID`
|
||||
ID=1000
|
||||
export ID
|
||||
assert_uname_os 'Darwin host 23.1.0 arm64' "${OS_RELEASE}" 'darwin'
|
||||
unset ID
|
||||
|
||||
# DragonFly BSD has no `nvm` OS of its own: fall back to `uname -a` as before
|
||||
printf 'NAME="DragonFly"\nID=dragonfly\n' > "${OS_RELEASE}"
|
||||
assert_uname_os 'DragonFly host 6.4-RELEASE' "${OS_RELEASE}" ''
|
||||
|
||||
# an unlisted `ID` on a non-Linux kernel is not assumed to be Linux
|
||||
printf 'NAME=HardenedBSD\nID=hardenedbsd\n' > "${OS_RELEASE}"
|
||||
assert_uname_os 'FreeBSD host 14.0-STABLE' "${OS_RELEASE}" 'freebsd'
|
||||
printf 'NAME=Helios\nID=helios\n' > "${OS_RELEASE}"
|
||||
assert_uname_os 'SunOS host 5.11' "${OS_RELEASE}" 'sunos'
|
||||
|
||||
cleanup
|
||||
Reference in New Issue
Block a user