From 059785dc91c704b41203af20a2a878bea8592bc5 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Tue, 28 Jul 2026 12:03:52 +0100 Subject: [PATCH] Preserve locale during brew startup - Validate the locale charmap instead of trusting its name. - Reuse filtered Linux locale settings to avoid `locale -a`. --- Library/Homebrew/test/bash_spec.rb | 53 ++++++++++++++++++++++++++++++ Library/Homebrew/utils/os.sh | 26 +++++++++------ bin/brew | 2 ++ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/Library/Homebrew/test/bash_spec.rb b/Library/Homebrew/test/bash_spec.rb index 18194f8c6a..49e12ffebd 100644 --- a/Library/Homebrew/test/bash_spec.rb +++ b/Library/Homebrew/test/bash_spec.rb @@ -39,6 +39,59 @@ RSpec.describe "Bash" do end end + describe "setup-locale" do + it "uses the macOS locale charmap rather than the locale name", :needs_macos do + setup_locale = [ + "/bin/bash", "-c", <<~BASH, "bash", (HOMEBREW_LIBRARY_PATH/"utils/os.sh").to_s + source "$1" + locale() { + [[ "${LC_CTYPE:-${LANG:-}}" == "UTF-8" ]] && printf "UTF-8" || printf "US-ASCII" + } + setup-locale + printf "%s" "${LC_ALL-unset}" + BASH + ] + invalid_stdout, invalid_stderr, invalid_status = Open3.capture3( + { "LANG" => "C.utf8", "LC_CTYPE" => nil, "LC_ALL" => nil }, *setup_locale + ) + valid_stdout, valid_stderr, valid_status = Open3.capture3( + { "LANG" => nil, "LC_CTYPE" => "UTF-8", "LC_ALL" => nil }, *setup_locale + ) + + expect([invalid_stdout, invalid_stderr, invalid_status.success?, + valid_stdout, valid_stderr, valid_status.success?]) + .to eq(["en_US.UTF-8", "", true, "unset", "", true]) + end + + it "restores filtered Linux locale variables and removes their copies" do + stdout, stderr, status = Open3.capture3( + { "LANG" => nil, "LC_CTYPE" => nil, "LC_ALL" => nil }, + "/bin/bash", "-c", <<~'BASH', "bash", (HOMEBREW_LIBRARY_PATH/"utils/os.sh").to_s + source "$1" + HOMEBREW_MACOS= + HOMEBREW_LANG=C + HOMEBREW_LC_CTYPE=C + HOMEBREW_LC_ALL=C.UTF-8 + locale() { + if [[ "$1" == "charmap" ]] + then + [[ "${LC_ALL:-}" == "C.UTF-8" ]] && printf "UTF-8" || printf "US-ASCII" + else + printf "locale -a called\n" >&2 + printf "C.UTF-8\n" + fi + } + setup-locale + printf "%s\n" "${LANG-unset}" "${LC_CTYPE-unset}" "${LC_ALL-unset}" \ + "${HOMEBREW_LANG-unset}" "${HOMEBREW_LC_CTYPE-unset}" "${HOMEBREW_LC_ALL-unset}" + BASH + ) + + expect([stdout, stderr, status.success?]) + .to eq(["C\nC\nC.UTF-8\nunset\nunset\nunset\n", "", true]) + end + end + describe "every `.sh` file" do it "has valid Bash syntax" do Pathname.glob("#{HOMEBREW_LIBRARY_PATH}/**/*.sh").each do |path| diff --git a/Library/Homebrew/utils/os.sh b/Library/Homebrew/utils/os.sh index b675e16ca2..08c804d112 100644 --- a/Library/Homebrew/utils/os.sh +++ b/Library/Homebrew/utils/os.sh @@ -99,24 +99,30 @@ then fi # Force UTF-8 to avoid encoding issues for users with broken locale settings. -# The locale is checked by name to avoid forking `locale charmap`. When -# bin/brew has filtered the environment no locale variables survive so a UTF-8 -# one must be set. +# Validate the active locale's charmap rather than trusting its name before +# selecting a usable fallback. setup-locale() { - local utf8_locale_regex='\.([Uu][Tt][Ff]-?8)(@|$)' local locales c_utf_regex en_us_regex utf_regex - if [[ "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" =~ ${utf8_locale_regex} ]] + if [[ -z "${HOMEBREW_MACOS}" ]] then - # The locale is already UTF-8. - : - elif [[ -n "${HOMEBREW_MACOS}" ]] + [[ -z "${HOMEBREW_LANG:-}" ]] || export LANG="${HOMEBREW_LANG}" + [[ -z "${HOMEBREW_LC_CTYPE:-}" ]] || export LC_CTYPE="${HOMEBREW_LC_CTYPE}" + [[ -z "${HOMEBREW_LC_ALL:-}" ]] || export LC_ALL="${HOMEBREW_LC_ALL}" + fi + unset HOMEBREW_LANG HOMEBREW_LC_CTYPE HOMEBREW_LC_ALL + + if [[ -n "${HOMEBREW_MACOS}" ]] then - export LC_ALL="en_US.UTF-8" + if [[ -z "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" ]] || [[ "$(locale charmap)" != "UTF-8" ]] + then + export LC_ALL="en_US.UTF-8" + fi else if ! command -v locale >/dev/null then export LC_ALL=C - else + elif [[ "$(locale charmap)" != "UTF-8" ]] + then locales="$(locale -a)" c_utf_regex='\bC\.(utf8|UTF-8)\b' en_us_regex='\ben_US\.(utf8|UTF-8)\b' diff --git a/bin/brew b/bin/brew index b9ef19d19c..46b6b39c3e 100755 --- a/bin/brew +++ b/bin/brew @@ -235,6 +235,8 @@ USED_BY_HOMEBREW_VARS=( GOBIN GOPATH LANG + LC_ALL + LC_CTYPE NODENV_ROOT PATH PYENV_ROOT