Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions bundler/lib/bundler/plugin/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ def installed_in_plugin_root?(name)
# @param [Pathname] index file path
# @param [Boolean] is the index file global index
def load_index(index_file, global = false)
base = base_for_index(global)

SharedHelpers.filesystem_access(index_file, :read) do |index_f|
valid_file = index_f&.exist? && !index_f.size.zero?
break unless valid_file
Expand All @@ -168,8 +170,8 @@ def load_index(index_file, global = false)

@commands.merge!(index["commands"])
@hooks.merge!(index["hooks"])
@load_paths.merge!(index["load_paths"])
@plugin_paths.merge!(index["plugin_paths"])
@load_paths.merge!(absolutize_load_paths(index["load_paths"], base))
@plugin_paths.merge!(absolutize_paths(index["plugin_paths"], base))
@sources.merge!(index["sources"]) unless global
end
end
Expand All @@ -178,11 +180,13 @@ def load_index(index_file, global = false)
# instance variables in YAML format. (The instance variables are supposed
# to be only String key value pairs)
def save_index
base = base_for_index(false)

index = {
"commands" => @commands,
"hooks" => @hooks,
"load_paths" => @load_paths,
"plugin_paths" => @plugin_paths,
"load_paths" => relativize_load_paths(@load_paths, base),
"plugin_paths" => relativize_paths(@plugin_paths, base),
"sources" => @sources,
}

Expand All @@ -192,6 +196,60 @@ def save_index
File.open(index_f, "w") {|f| f.puts YAMLSerializer.dump(index) }
end
end

def base_for_index(global)
global ? Plugin.global_root : Plugin.root
end

def relativize_paths(paths, base)
return paths unless paths

paths.transform_values do |path|
relativize_path(path, base)
end
end

def relativize_load_paths(paths, base)
return paths unless paths

paths.transform_values do |path_list|
Array(path_list).map {|path| relativize_path(path, base) }
end
end

def absolutize_paths(paths, base)
return {} unless paths

paths.transform_values do |path|
absolutize_path(path, base)
end
end

def absolutize_load_paths(paths, base)
return {} unless paths

paths.transform_values do |path_list|
Array(path_list).map {|path| absolutize_path(path, base) }
end
end

def relativize_path(path, base)
pathname = Pathname.new(path)
return path unless pathname.absolute?

base_path = Pathname.new(base)
if pathname == base_path || pathname.to_s.start_with?(base_path.to_s + File::SEPARATOR)
pathname.relative_path_from(base_path).to_s
else
path
end
end
Comment on lines +236 to +246
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The relativize_path method doesn't handle ArgumentError that can be raised by relative_path_from on Windows when paths are on different drives. Other parts of the codebase (e.g., bundler/lib/bundler/shared_helpers.rb:228-233) rescue this exception and return the original path. Consider adding similar error handling here to ensure cross-platform compatibility.

Copilot uses AI. Check for mistakes.

def absolutize_path(path, base)
pathname = Pathname.new(path)
pathname = Pathname.new(base).join(pathname) unless pathname.absolute?
pathname.to_s
end
end
end
end
44 changes: 44 additions & 0 deletions bundler/spec/bundler/plugin/index_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,48 @@
include_examples "it cleans up"
end
end

describe "relative plugin paths" do
let(:plugin_name) { "relative-plugin" }

before do
Bundler::Plugin.reset!
allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)

plugin_root = Bundler::Plugin.root
FileUtils.mkdir_p(plugin_root)

path = plugin_root.join(plugin_name)
FileUtils.mkdir_p(path.join("lib"))

index.register_plugin(plugin_name, path.to_s, [path.join("lib").to_s], [], [], [])
end

it "stores plugin paths relative to the plugin root" do
require "yaml"
data = YAML.load_file(index.index_file)

expect(data["plugin_paths"][plugin_name]).to eq(plugin_name)
expect(data["load_paths"][plugin_name]).to eq([File.join(plugin_name, "lib")])
end

it "expands relative paths when the plugin root changes" do
old_index_file = index.index_file

new_root = tmp.join("moved_plugin_root")
FileUtils.mkdir_p(new_root)
FileUtils.cp(old_index_file, new_root.join("index"))

Bundler::Plugin.reset!
Bundler::Plugin.instance_variable_set(:@root, nil)
allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
allow(Bundler::Plugin).to receive(:root).and_return(new_root)
allow(Bundler::Plugin).to receive(:local_root).and_return(new_root)

new_index = Index.new

expect(new_index.plugin_path(plugin_name)).to eq(new_root.join(plugin_name))
expect(new_index.load_paths(plugin_name)).to eq([new_root.join(plugin_name, "lib").to_s])
end
end
end