From a4f801ed72d71b945a5c0e1b28a40eab581e7e94 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 10 Sep 2026 17:08:50 -0700 Subject: [PATCH] [Fix] `nvm_get_make_jobs`: count cores on platforms the `case` does not name `nvm_get_os` also returns `win` (Cygwin/MSYS/MinGW) and the empty string, neither of which had an arm, so `NVM_CPU_CORES` went unassigned and nvm asked the user to report a gap it already knew about. On an unrecognized `uname` that also meant a single-threaded source build; `win` refuses source builds a few lines later either way. `NUMBER_OF_PROCESSORS` comes last because Windows scopes it to the calling process' processor group. Assigning `NVM_CPU_CORES` in every arm also keeps the `nvm_is_natural_num` check below from aborting dash and ksh under `set -u`. --- nvm.sh | 10 ++ test/fast/Unit tests/nvm_get_make_jobs | 147 +++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100755 test/fast/Unit tests/nvm_get_make_jobs diff --git a/nvm.sh b/nvm.sh index 429911cb..f70b6b4e 100755 --- a/nvm.sh +++ b/nvm.sh @@ -2877,6 +2877,16 @@ nvm_get_make_jobs() { "_aix") NVM_CPU_CORES="$(pmcycles -m | wc -l)" ;; + *) + # `_NPROCESSORS_ONLN` is how glibc, FreeBSD, macOS, and Cygwin spell it; + # the unprefixed name is what POSIX.1-2024 standardizes, and what NetBSD + # and Solaris answer to. Windows shells inherit `NUMBER_OF_PROCESSORS`; + # the smallest ones ship neither `getconf` nor `nproc`. + NVM_CPU_CORES="$(command getconf _NPROCESSORS_ONLN 2>/dev/null \ + || command getconf NPROCESSORS_ONLN 2>/dev/null \ + || command nproc 2>/dev/null \ + || nvm_echo "${NUMBER_OF_PROCESSORS-}")" + ;; esac if ! nvm_is_natural_num "${NVM_CPU_CORES}"; then nvm_err 'Can not determine how many core(s) are available, running in single-threaded mode.' diff --git a/test/fast/Unit tests/nvm_get_make_jobs b/test/fast/Unit tests/nvm_get_make_jobs new file mode 100755 index 00000000..481e1363 --- /dev/null +++ b/test/fast/Unit tests/nvm_get_make_jobs @@ -0,0 +1,147 @@ +#!/bin/sh + +# `nvm_get_make_jobs` sets the global `NVM_MAKE_JOBS` instead of echoing it, so +# every case runs it in this shell, with its output captured to files. + +ORIG_PATH="${PATH}" +TMP_DIR="$(mktemp -d)" + +# the probe shims live under `mktemp -d` rather than `.`, because this directory +# is shared with every other unit test and a leaked `getconf` would poison them +SHIMS="${TMP_DIR}/bin" +OUT="${TMP_DIR}/out" +ERR="${TMP_DIR}/err" + +cleanup () { + export PATH="${ORIG_PATH}" + rm -rf "${TMP_DIR}" +} + +die () { cleanup; echo "$@" ; exit 1; } + +: nvm.sh +\. ../../../nvm.sh + +mkdir -p "${SHIMS}" + +shim () { + { + echo '#!/bin/sh' + echo "$2" + } > "${SHIMS}/$1" + chmod +x "${SHIMS}/$1" +} + +# a probe that behaves as if it were not installed, and whose complaint must +# never reach the user +shim_absent () { + shim "$1" "echo 'PROBE_NOISE: $1' >&2; exit 127" +} + +REAL_OS="$(nvm_get_os)" + +FAKE_OS='' +nvm_get_os () { + nvm_echo "${FAKE_OS}" +} + +run () { + FAKE_OS="$1" + shift + NVM_MAKE_JOBS='' + nvm_get_make_jobs "$@" >"${OUT}" 2>"${ERR}" +} + +assert_jobs () { + [ "_${NVM_MAKE_JOBS}" = "_$1" ] \ + || die "${2}: expected NVM_MAKE_JOBS ${1}, got '${NVM_MAKE_JOBS}'" +} + +assert_stdout () { + ACTUAL="$(cat "${OUT}")" + [ "_${ACTUAL}" = "_$1" ] \ + || die "${2}: expected stdout '${1}', got '${ACTUAL}'" +} + +# deliberately narrower than "stderr is empty": ksh93 has no `local`, so every +# nvm.sh function already sprays `local: not found` here (see #574) +assert_no_probe_noise () { + ! nvm_grep -q 'PROBE_NOISE' "${ERR}" \ + || die "${1}: a failed probe leaked to stderr: '$(cat "${ERR}")'" +} + +assert_not_nagged () { + ! nvm_grep -q 'Please report an issue on GitHub' "${ERR}" \ + || die "${1}: unexpected report-an-issue message" +} + +assert_nagged () { + nvm_grep -q 'Please report an issue on GitHub' "${ERR}" \ + || die "${1}: expected the report-an-issue message, got '$(cat "${ERR}")'" +} + +export PATH="${SHIMS}:${ORIG_PATH}" + +# an OS the `case` does not name still gets a real core count +shim getconf 'echo 4' +shim_absent nproc + +run win +assert_jobs 3 'win' +assert_stdout 'Detected that you have 4 CPU core(s) +Running with 3 threads to speed up the build' 'win' +assert_not_nagged 'win' +assert_no_probe_noise 'win' + +run '' +assert_jobs 3 'unknown OS' +assert_not_nagged 'unknown OS' + +# Solaris and NetBSD spell the `getconf` variable without the leading underscore +shim getconf 'case "$1" in NPROCESSORS_ONLN) echo 4 ;; *) exit 1 ;; esac' +run win +assert_jobs 3 'unprefixed getconf name' +assert_not_nagged 'unprefixed getconf name' + +# with no `getconf`, `nproc` answers +shim_absent getconf +shim nproc 'echo 4' +run win +assert_jobs 3 'nproc' +assert_not_nagged 'nproc' +assert_no_probe_noise 'nproc' + +# Cygwin/MSYS/MinGW inherit `NUMBER_OF_PROCESSORS` from Windows +shim_absent nproc +NUMBER_OF_PROCESSORS=4 +run win +assert_jobs 3 'NUMBER_OF_PROCESSORS' +assert_not_nagged 'NUMBER_OF_PROCESSORS' +assert_no_probe_noise 'NUMBER_OF_PROCESSORS' + +# with nothing to probe, nvm still says so instead of guessing +unset NUMBER_OF_PROCESSORS +run win +assert_jobs 1 'no probes' +assert_nagged 'no probes' + +# the named arms keep priority: darwin asks `sysctl`, not `getconf` +shim getconf 'echo 99' +shim sysctl 'echo 4' +run darwin +assert_jobs 3 'darwin uses sysctl' +assert_not_nagged 'darwin uses sysctl' + +export PATH="${ORIG_PATH}" + +# an explicit count short-circuits detection entirely +run '' 7 +assert_jobs 7 'explicit jobs' +assert_stdout 'number of `make` jobs: 7' 'explicit jobs' + +# on the real host, detected or not, the result is always usable +run "${REAL_OS}" +nvm_is_natural_num "${NVM_MAKE_JOBS}" \ + || die "real host: expected a natural number, got '${NVM_MAKE_JOBS}'" + +cleanup