From 2b4324af9b6b272a914998fdf8b025212e23b44d Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Thu, 3 Apr 2025 12:47:21 +0100 Subject: [PATCH] Update Linux GCC code. Update both the variables that dictate this and the documents that explain our GCC/glibc policies. These should ease a future migration to a newer GCC version. --- Library/Homebrew/development_tools.rb | 5 +++++ .../extend/os/linux/development_tools.rb | 22 +++++++++++++------ .../Homebrew/extend/os/linux/system_config.rb | 4 ++-- .../Homebrew/test/compiler_selector_spec.rb | 6 ++--- docs/Linux-CI.md | 12 +++++----- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/Library/Homebrew/development_tools.rb b/Library/Homebrew/development_tools.rb index 84f743954a..0b0af856d1 100644 --- a/Library/Homebrew/development_tools.rb +++ b/Library/Homebrew/development_tools.rb @@ -102,6 +102,11 @@ class DevelopmentTools end, T.nilable(Version)) end + sig { returns(Pathname) } + def host_gcc_path + Pathname.new("/usr/bin/gcc") + end + # Get the GCC version. # # @api internal diff --git a/Library/Homebrew/extend/os/linux/development_tools.rb b/Library/Homebrew/extend/os/linux/development_tools.rb index bd07e5d03a..1fdc5945ab 100644 --- a/Library/Homebrew/extend/os/linux/development_tools.rb +++ b/Library/Homebrew/extend/os/linux/development_tools.rb @@ -37,17 +37,25 @@ module OS @needs_libc_formula = !!@needs_libc_formula end + # Keep this method around for now to make it easier to add this functionality later. + # rubocop:disable Style/UselessMethodDefinition + sig { returns(Pathname) } + def host_gcc_path + # TODO: override this if/when we to pick the GCC based on e.g. the Ubuntu version. + super + end + # rubocop:enable Style/UselessMethodDefinition + sig { returns(T::Boolean) } def needs_compiler_formula? return @needs_compiler_formula unless @needs_compiler_formula.nil? - gcc = "/usr/bin/gcc" - @needs_compiler_formula = T.let(if File.exist?(gcc) - ::DevelopmentTools.gcc_version(gcc) < OS::LINUX_GCC_CI_VERSION - else - true - end, T.nilable(T::Boolean)) - !!@needs_compiler_formula + @needs_compiler_formula = T.let(nil, T.nilable(T::Boolean)) + @needs_compiler_formula = if host_gcc_path.exist? + ::DevelopmentTools.gcc_version(host_gcc_path.to_s) < OS::LINUX_GCC_CI_VERSION + else + true + end end sig { returns(T::Hash[String, T.nilable(String)]) } diff --git a/Library/Homebrew/extend/os/linux/system_config.rb b/Library/Homebrew/extend/os/linux/system_config.rb index 8f99163454..13ded66558 100644 --- a/Library/Homebrew/extend/os/linux/system_config.rb +++ b/Library/Homebrew/extend/os/linux/system_config.rb @@ -19,7 +19,7 @@ module SystemConfig end def host_gcc_version - gcc = Pathname.new "/usr/bin/gcc" + gcc = DevelopmentTools.host_gcc_path return "N/A" unless gcc.executable? `#{gcc} --version 2>/dev/null`[/ (\d+\.\d+\.\d+)/, 1] @@ -47,7 +47,7 @@ module SystemConfig out.puts "OS: #{OS::Linux.os_version}" out.puts "WSL: #{OS::Linux.wsl_version}" if OS::Linux.wsl? out.puts "Host glibc: #{host_glibc_version}" - out.puts "/usr/bin/gcc: #{host_gcc_version}" + out.puts "#{DevelopmentTools.host_gcc_path}: #{host_gcc_version}" out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH ["glibc", CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f| out.puts "#{f}: #{formula_linked_version(f)}" diff --git a/Library/Homebrew/test/compiler_selector_spec.rb b/Library/Homebrew/test/compiler_selector_spec.rb index e95e4c2e72..a4fe2a9956 100644 --- a/Library/Homebrew/test/compiler_selector_spec.rb +++ b/Library/Homebrew/test/compiler_selector_spec.rb @@ -49,7 +49,7 @@ RSpec.describe CompilerSelector do it "returns gcc-10 if gcc formula offers gcc-10 on linux", :needs_linux do software_spec.fails_with(:clang) allow(Formulary).to receive(:factory) - .with("gcc@11") + .with(OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA) .and_return(instance_double(Formula, version: Version.new("10.0"))) expect(selector.compiler).to eq("gcc-10") end @@ -59,7 +59,7 @@ RSpec.describe CompilerSelector do software_spec.fails_with(gcc: "10") software_spec.fails_with(gcc: "12") allow(Formulary).to receive(:factory) - .with("gcc@11") + .with(OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA) .and_return(instance_double(Formula, version: Version.new("10.0"))) expect(selector.compiler).to eq("gcc-11") end @@ -68,7 +68,7 @@ RSpec.describe CompilerSelector do software_spec.fails_with(:clang) software_spec.fails_with(:gcc) { version "11" } allow(Formulary).to receive(:factory) - .with("gcc@11") + .with(OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA) .and_return(instance_double(Formula, version: Version.new("11.0"))) expect(selector.compiler).to eq("gcc-12") end diff --git a/docs/Linux-CI.md b/docs/Linux-CI.md index 1e619e9925..87187bb063 100644 --- a/docs/Linux-CI.md +++ b/docs/Linux-CI.md @@ -1,5 +1,5 @@ --- -last_review_date: "1970-01-01" +last_review_date: "2025-03-28" --- # Linux CI in `homebrew/core` @@ -20,25 +20,23 @@ We have moved our CI to Ubuntu 22.04 Moving from Ubuntu 16.04 to Ubuntu 22.04 (and thus skipping version 18.04 and 20.04) took longer than expected. -We plan to proceed with regular updates from 2022 onwards. We aim to use the latest Ubuntu LTS version for our CI. - -We will start using the latest Ubuntu LTS version for our CI no earlier than 3 months after its release and, ideally, no more than 12 months after its release. +We plan to proceed with regular updates from 2022 onwards. We aim to use the oldest supported Ubuntu LTS version for our CI that provides the GCC version we need. | Distribution | Glibc | GCC | LTS standard security maintenance | |---|---|---|---| | Ubuntu 14.04 | 2.19 | 4 | From 2014 to 2017 | | Ubuntu 16.04 | 2.23 | 5 | From 2017 to 2022 | | Ubuntu 20.04 | 2.31 | 5 | From 2020 to 2025 | -| Ubuntu 22.04 | 2.35 | 11 | From 2022 to 2027 | +| Ubuntu 22.04 | 2.35 | 11 (provides 12) | From 2022 to 2027 | | Ubuntu 24.04 | 2.39 | 13 | From 2024 to 2029 | | Ubuntu 26.04 | ? | ? | ? | [Source](https://ubuntu.com/about/release-cycle) -## Why always use the latest version? +## Why upgrade to a newer version? Homebrew is a rolling-release package manager. We try to ship the newest things as quickly as possible, on macOS and Linux. When a formula needs a newer GCC because our host GCC in CI is too old, we needed to make that formula depend on a newer Homebrew GCC. All C++ dependents of that formula immediately acquire a dependency on Homebrew GCC as well. While we have taken the steps to make sure this no longer holds up GCC updates, it still creates a maintenance burden. This problem is more likely for formula which are very actively maintained and try to use newer features of C++. We decided that we shouldn't have a maintenance burden for formulae which are doing the right thing by staying up to date. It makes a lot of sense for Homebrew maintainers to submit upstream fixes when formulae are not working with newer compilers. It makes a lot less sense for Homebrew maintainers to submit fixes because our host compiler is too old. -Note that `glibc` will need to be installed for more users as their `glibc` version will often be too old: disk space is cheap and we have can handle this situation for our users. This situation will often arise when update to a new LTS version and adoption of the new Ubuntu is still low during the first months. For the same reasons as above: we prefer to stay on the bleeding edge and give our users a gentle nudge to think about updating their OS. +Note that `glibc` will need to be installed for more users as their `glibc` version will often be too old. This is not as smooth as using a newer GCC as we don't test this configuration in CI. This is why we want to balance the newest GCC with a more conservative `glibc`.