From a959228644c26a82c76e4f47b5b9c02b12bded6b Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Sun, 26 Jul 2026 18:13:04 +0100 Subject: [PATCH] Add gzipped executable action Several binary formulae ship a gzipped executable that needs the same guarded post-install expansion and mode handling. - resolve serialised source and destination path bases - skip absent payloads and replace stale expanded sources - install each executable with an explicit literal permission mode --- Library/Homebrew/install_steps.rb | 16 +++++++++ .../Homebrew/install_steps/formula_actions.rb | 24 +++++++++++++ .../rubocops/shared/install_steps_helper.rb | 2 +- Library/Homebrew/test/install_steps_spec.rb | 35 +++++++++++++++++++ .../test/rubocops/install_steps_spec.rb | 7 ++-- docs/Formula-Cookbook.md | 1 + 6 files changed, 81 insertions(+), 4 deletions(-) diff --git a/Library/Homebrew/install_steps.rb b/Library/Homebrew/install_steps.rb index 615333a459..5986efde21 100644 --- a/Library/Homebrew/install_steps.rb +++ b/Library/Homebrew/install_steps.rb @@ -608,6 +608,20 @@ module Homebrew add_step("configure_gcc_runtime") end + sig { + params( + source: ::T.any(::String, ::Pathname), + target: ::T.any(::String, ::Pathname), + source_base: ::T.nilable(::T.any(::String, ::Symbol)), + target_base: ::T.nilable(::T.any(::String, ::Symbol)), + ).void + } + def install_gzipped_executable(source, target, source_base: nil, target_base: nil) + add_step("install_gzipped_executable", + "source" => path_spec(source, base: source_base, default_base: @default_source_base), + "target" => path_spec(target, base: target_base, default_base: @default_target_base)) + end + private sig { params(guard: PathSpec, block: ::T.proc.void).void } @@ -852,6 +866,8 @@ module Homebrew opoo expand_template_tokens(step_string(step, "message")) when "configure_gcc_runtime" run_configure_gcc_runtime + when "install_gzipped_executable" + run_install_gzipped_executable(step) when "set_permissions" run_set_permissions(step) when "set_ownership" diff --git a/Library/Homebrew/install_steps/formula_actions.rb b/Library/Homebrew/install_steps/formula_actions.rb index d620ee7f60..f647a026f9 100644 --- a/Library/Homebrew/install_steps/formula_actions.rb +++ b/Library/Homebrew/install_steps/formula_actions.rb @@ -60,6 +60,30 @@ module Homebrew EOS specs.write(specs.read.gsub(" %o ", "\\0%(homebrew_rpath) ")) if homebrew_rpath end + + sig { params(step: Step).void } + def run_install_gzipped_executable(step) + source = resolve_path(step_path(step, "source")) + return unless source.exist? + + require "zlib" + + target = resolve_path(step_path(step, "target")) + target.dirname.mkpath + temporary_target = target.dirname/".#{target.basename}.install-step" + FileUtils.rm_f temporary_target + begin + Zlib::GzipReader.open(source.to_s) do |gzip| + IO.copy_stream(gzip, temporary_target.to_s) + end + FileUtils.rm_f target + FileUtils.mv temporary_target, target + source.unlink + ensure + FileUtils.rm_f temporary_target + end + target.chmod 0755 + end end end end diff --git a/Library/Homebrew/rubocops/shared/install_steps_helper.rb b/Library/Homebrew/rubocops/shared/install_steps_helper.rb index 7d72799c20..5094d2ae64 100644 --- a/Library/Homebrew/rubocops/shared/install_steps_helper.rb +++ b/Library/Homebrew/rubocops/shared/install_steps_helper.rb @@ -17,7 +17,7 @@ module RuboCop PERMISSION_STEP_METHODS = [:set_permissions, :set_ownership].freeze COMMAND_STEP_METHODS = [:run, :terminate_process].freeze NOTICE_STEP_METHODS = [:warn].freeze - FORMULA_ACTION_STEP_METHODS = [:configure_gcc_runtime].freeze + FORMULA_ACTION_STEP_METHODS = [:configure_gcc_runtime, :install_gzipped_executable].freeze STEP_SCOPE_METHODS = [:if_path_exists, :unless_path_exists, :on_macos, :on_linux].freeze ALLOWED_STEP_METHODS = T.let( [*FILE_PREPARATION_STEP_METHODS, *LINK_STEP_METHODS, *CONFIG_WRITE_STEP_METHODS, *SERVICE_DATA_STEP_METHODS, diff --git a/Library/Homebrew/test/install_steps_spec.rb b/Library/Homebrew/test/install_steps_spec.rb index 6307f5f5e9..951e263e13 100644 --- a/Library/Homebrew/test/install_steps_spec.rb +++ b/Library/Homebrew/test/install_steps_spec.rb @@ -770,6 +770,41 @@ RSpec.describe Homebrew::InstallSteps do expect(specs.read).to include("%(homebrew_rpath)", "-idirafter /usr/include/x86_64-linux-gnu") end + specify "dispatches gzipped executable installation" do + steps = Homebrew::InstallSteps::DSL.build do + install_gzipped_executable "compressed.gz", "bin/executable" + end + + runner = Homebrew::InstallSteps::Runner.new(context:) + expect(runner).to receive(:run_install_gzipped_executable) + + runner.run(steps) + end + + specify "installs a gzipped executable with a fixed mode", :aggregate_failures do + require "zlib" + + source = root/"prefix/bin/executable.gz" + source.dirname.mkpath + Zlib::GzipWriter.open(source.to_s) do |gzip| + gzip.orig_name = "stored-name" + gzip.write "executable" + end + stored_name = source.dirname/"stored-name" + stored_name.write "preserve" + steps = Homebrew::InstallSteps::DSL.build(default_source_base: :prefix, default_target_base: :prefix) do + install_gzipped_executable "bin/executable.gz", "bin/executable" + end + + Homebrew::InstallSteps::Runner.new(context:).run(steps) + + target = root/"prefix/bin/executable" + expect(target.read).to eq("executable") + expect(target.stat.mode & 0777).to eq(0755) + expect(source).not_to exist + expect(stored_name.read).to eq("preserve") + end + describe "runs gtk_update_icon_cache rebuild action" do let(:formula) { instance_double(Formula, opt_bin: root/"opt/bin") } let(:steps) do diff --git a/Library/Homebrew/test/rubocops/install_steps_spec.rb b/Library/Homebrew/test/rubocops/install_steps_spec.rb index fef2f77773..1bf2786b6d 100644 --- a/Library/Homebrew/test/rubocops/install_steps_spec.rb +++ b/Library/Homebrew/test/rubocops/install_steps_spec.rb @@ -42,7 +42,7 @@ RSpec.describe RuboCop::Cop::FormulaAudit::InstallSteps do post_install_steps do system "true" - ^^^^^^^^^^^^^ FormulaAudit/InstallSteps: Steps blocks may only contain install step DSL calls: `mkdir`, `mkdir_p`, `touch`, `move`, `mv`, `move_children`, `move_contents`, `copy`, `remove`, `inreplace`, `symlink`, `ln_s`, `ln_sf`, `link_dir`, `link_children`, `write`, `init_data_dir`, `compile_gsettings_schemas`, `gio_querymodules`, `gdk_pixbuf_query_loaders`, `gtk_update_icon_cache`, `update_mime_database`, `update_desktop_database`, `set_permissions`, `run`, `terminate_process`, `warn`, `configure_gcc_runtime`, `if_path_exists`, `unless_path_exists`, `on_macos`, `on_linux`. + ^^^^^^^^^^^^^ FormulaAudit/InstallSteps: Steps blocks may only contain install step DSL calls: `mkdir`, `mkdir_p`, `touch`, `move`, `mv`, `move_children`, `move_contents`, `copy`, `remove`, `inreplace`, `symlink`, `ln_s`, `ln_sf`, `link_dir`, `link_children`, `write`, `init_data_dir`, `compile_gsettings_schemas`, `gio_querymodules`, `gdk_pixbuf_query_loaders`, `gtk_update_icon_cache`, `update_mime_database`, `update_desktop_database`, `set_permissions`, `run`, `terminate_process`, `warn`, `configure_gcc_runtime`, `install_gzipped_executable`, `if_path_exists`, `unless_path_exists`, `on_macos`, `on_linux`. end end RUBY @@ -68,6 +68,7 @@ RSpec.describe RuboCop::Cop::FormulaAudit::InstallSteps do terminate_process "foo", attempts: 3 warn "foo exists" configure_gcc_runtime + install_gzipped_executable "compressed.gz", "bin/executable" write "foo/banner", <<~TEXT literal banner TEXT @@ -103,7 +104,7 @@ RSpec.describe RuboCop::Cop::FormulaAudit::InstallSteps do post_install_steps do on_macos do system "true" - ^^^^^^^^^^^^^ FormulaAudit/InstallSteps: Steps blocks may only contain install step DSL calls: `mkdir`, `mkdir_p`, `touch`, `move`, `mv`, `move_children`, `move_contents`, `copy`, `remove`, `inreplace`, `symlink`, `ln_s`, `ln_sf`, `link_dir`, `link_children`, `write`, `init_data_dir`, `compile_gsettings_schemas`, `gio_querymodules`, `gdk_pixbuf_query_loaders`, `gtk_update_icon_cache`, `update_mime_database`, `update_desktop_database`, `set_permissions`, `run`, `terminate_process`, `warn`, `configure_gcc_runtime`, `if_path_exists`, `unless_path_exists`, `on_macos`, `on_linux`. + ^^^^^^^^^^^^^ FormulaAudit/InstallSteps: Steps blocks may only contain install step DSL calls: `mkdir`, `mkdir_p`, `touch`, `move`, `mv`, `move_children`, `move_contents`, `copy`, `remove`, `inreplace`, `symlink`, `ln_s`, `ln_sf`, `link_dir`, `link_children`, `write`, `init_data_dir`, `compile_gsettings_schemas`, `gio_querymodules`, `gdk_pixbuf_query_loaders`, `gtk_update_icon_cache`, `update_mime_database`, `update_desktop_database`, `set_permissions`, `run`, `terminate_process`, `warn`, `configure_gcc_runtime`, `install_gzipped_executable`, `if_path_exists`, `unless_path_exists`, `on_macos`, `on_linux`. end end end @@ -117,7 +118,7 @@ RSpec.describe RuboCop::Cop::FormulaAudit::InstallSteps do post_install_steps do write "foo.conf", "prefix = #{prefix}" - ^^^^^^^^^ FormulaAudit/InstallSteps: Steps blocks may only contain install step DSL calls: `mkdir`, `mkdir_p`, `touch`, `move`, `mv`, `move_children`, `move_contents`, `copy`, `remove`, `inreplace`, `symlink`, `ln_s`, `ln_sf`, `link_dir`, `link_children`, `write`, `init_data_dir`, `compile_gsettings_schemas`, `gio_querymodules`, `gdk_pixbuf_query_loaders`, `gtk_update_icon_cache`, `update_mime_database`, `update_desktop_database`, `set_permissions`, `run`, `terminate_process`, `warn`, `configure_gcc_runtime`, `if_path_exists`, `unless_path_exists`, `on_macos`, `on_linux`. + ^^^^^^^^^ FormulaAudit/InstallSteps: Steps blocks may only contain install step DSL calls: `mkdir`, `mkdir_p`, `touch`, `move`, `mv`, `move_children`, `move_contents`, `copy`, `remove`, `inreplace`, `symlink`, `ln_s`, `ln_sf`, `link_dir`, `link_children`, `write`, `init_data_dir`, `compile_gsettings_schemas`, `gio_querymodules`, `gdk_pixbuf_query_loaders`, `gtk_update_icon_cache`, `update_mime_database`, `update_desktop_database`, `set_permissions`, `run`, `terminate_process`, `warn`, `configure_gcc_runtime`, `install_gzipped_executable`, `if_path_exists`, `unless_path_exists`, `on_macos`, `on_linux`. end end RUBY diff --git a/docs/Formula-Cookbook.md b/docs/Formula-Cookbook.md index 9e8dfea62f..29f586d4ff 100644 --- a/docs/Formula-Cookbook.md +++ b/docs/Formula-Cookbook.md @@ -1118,6 +1118,7 @@ end Use the named actions below for formula families that share post-install algorithms. Unique complex logic should be installed as a packaged helper and invoked with `run` instead of adding a formula-specific action. * `configure_gcc_runtime`: generate the Linux GCC runtime links and specs. +* `install_gzipped_executable`: unpack and install a gzipped executable. #### Service data directory steps