From 034cc23c1323001cf48546129dff78561f00fe40 Mon Sep 17 00:00:00 2001 From: Marvin Frick Date: Thu, 17 Oct 2024 12:21:32 +0200 Subject: [PATCH 1/2] Add new hooks around fetching gems and git sources --- bundler/lib/bundler/plugin/events.rb | 24 ++++++++++++++++++++++++ bundler/lib/bundler/source/git.rb | 4 ++++ bundler/lib/bundler/source/rubygems.rb | 5 ++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/bundler/lib/bundler/plugin/events.rb b/bundler/lib/bundler/plugin/events.rb index 29c05098ae14..0197a147cb9e 100644 --- a/bundler/lib/bundler/plugin/events.rb +++ b/bundler/lib/bundler/plugin/events.rb @@ -45,6 +45,30 @@ def self.defined_event?(event) # GEM_AFTER_INSTALL = "after-install" define :GEM_AFTER_INSTALL, "after-install" + # @!parse + # A hook called before each individual gem is fetched for installation. + # Includes a Bundler::ParallelInstaller::SpecInstallation and a source reference. + # GEM_BEFORE_FETCH = "before-fetch" + define :GEM_BEFORE_FETCH, "before-fetch" + + # @!parse + # A hook called after each individual gem is fetched for installation. + # Includes a Bundler::ParallelInstaller::SpecInstallation and a source reference. + # GEM_AFTER_FETCH = "after-fetch" + define :GEM_AFTER_FETCH, "after-fetch" + + # @!parse + # A hook called before a git source is fetched. + # Includes a Bundler::Source::Git reference. + # GIT_GEM_BEFORE_FETCH = "git-before-fetch" + define :GIT_BEFORE_FETCH, "git-before-fetch" + + # @!parse + # A hook called after a git source is fetched. + # Includes a Bundler::Source::Git reference. + # GEM_AFTER_FETCH = "git-after-fetch" + define :GIT_AFTER_FETCH, "git-after-fetch" + # @!parse # A hook called before any gems install # Includes an Array of Bundler::Dependency objects diff --git a/bundler/lib/bundler/source/git.rb b/bundler/lib/bundler/source/git.rb index fde05e472b3e..bf62f4bd076a 100644 --- a/bundler/lib/bundler/source/git.rb +++ b/bundler/lib/bundler/source/git.rb @@ -191,8 +191,10 @@ def specs(*) set_cache_path!(app_cache_path) if use_app_cache? if requires_checkout? && !@copied + Plugin.hook(Plugin::Events::GIT_BEFORE_FETCH, self) fetch unless use_app_cache? checkout + Plugin.hook(Plugin::Events::GIT_AFTER_FETCH, self) end local_specs @@ -205,7 +207,9 @@ def install(spec, options = {}) print_using_message "Using #{version_message(spec, options[:previous_spec])} from #{self}" if (requires_checkout? && !@copied) || force + Plugin.hook(Plugin::Events::GEM_BEFORE_FETCH, spec, self) checkout + Plugin.hook(Plugin::Events::GEM_AFTER_FETCH, spec, self) end generate_bin_options = { disable_extensions: !spec.missing_extensions?, build_args: options[:build_args] } diff --git a/bundler/lib/bundler/source/rubygems.rb b/bundler/lib/bundler/source/rubygems.rb index 19800e9c585c..59bec0b49c00 100644 --- a/bundler/lib/bundler/source/rubygems.rb +++ b/bundler/lib/bundler/source/rubygems.rb @@ -431,11 +431,14 @@ def fetch_names(fetchers, dependency_names, index) end def fetch_gem_if_possible(spec, previous_spec = nil) - if spec.remote + Plugin.hook(Plugin::Events::GEM_BEFORE_FETCH, spec, self) + gem_path = if spec.remote fetch_gem(spec, previous_spec) else cached_gem(spec) end + Plugin.hook(Plugin::Events::GEM_AFTER_FETCH, spec, self) + gem_path end def fetch_gem(spec, previous_spec = nil) From 11fcf0da07a0cae7a7a6580d2ddafa7480817cb2 Mon Sep 17 00:00:00 2001 From: Marvin Frick Date: Sat, 8 Feb 2025 15:05:40 +0100 Subject: [PATCH 2/2] Add spec for before/after fetch hooks --- bundler/spec/plugins/hook_spec.rb | 73 +++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/bundler/spec/plugins/hook_spec.rb b/bundler/spec/plugins/hook_spec.rb index 3f9053bbc83d..bf565e3a41f0 100644 --- a/bundler/spec/plugins/hook_spec.rb +++ b/bundler/spec/plugins/hook_spec.rb @@ -1,6 +1,79 @@ # frozen_string_literal: true RSpec.describe "hook plugins" do + + context "before-and-after-fetch hooks" do + before do + build_repo2 do + build_plugin "fetch-timing-timing-plugin" do |s| + s.write "plugins.rb", <<-RUBY + @timing_start = nil + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_FETCH do |spec_install| + @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec_install.name} started fetch at \#{@timing_start}" + end + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_FETCH do |spec_install| + timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec_install.name} took \#{timing_end - @timing_start} to fetch" + @timing_start = nil + end + RUBY + end + end + + bundle "plugin install fetch-timing-timing-plugin --source https://gem.repo2" + end + + it "runs before and after each rubygem is installed" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + expect(out).to include "gem rake started fetch at" + expect(out).to match /gem rake took \d+\.\d+ to fetch/ + expect(out).to include "gem myrack started fetch at" + expect(out).to match /gem myrack took \d+\.\d+ to fetch/ + end + end + + context "before-and-after-git hooks" do + before do + build_repo2 do + build_plugin "fetch-timing-timing-plugin" do |s| + s.write "plugins.rb", <<-RUBY + @timing_start = nil + Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_BEFORE_FETCH do |spec_install| + @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec_install.name} started git fetch/checkout at \#{@timing_start}" + end + Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_AFTER_FETCH do |spec_install| + timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec_install.name} took \#{timing_end - @timing_start} to fetch" + @timing_start = nil + end + RUBY + end + end + + bundle "plugin install fetch-timing-timing-plugin --source https://gem.repo2" + end + + it "runs before and after each git source rubygem is installed" do + build_git "foo", "1.0", path: lib_path("foo") + + relative_path = lib_path("foo").relative_path_from(bundled_app) + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "foo", :git => "#{relative_path}" + G + + expect(out).to include "gem foo started git fetch/checkout at" + expect(out).to match /gem foo took \d+\.\d+ to fetch/ + end + end + context "before-install-all hook" do before do build_repo2 do