diff --git a/Library/Homebrew/cleaner.rb b/Library/Homebrew/cleaner.rb index 5df0e39b15865..8636ea7a0b4b3 100644 --- a/Library/Homebrew/cleaner.rb +++ b/Library/Homebrew/cleaner.rb @@ -59,6 +59,7 @@ def clean observe_file_removal info_dir_file end + rewrite_pkgconfig rewrite_shebangs clean_python_metadata @@ -153,6 +154,48 @@ def clean_dir(directory) end end + sig { void } + def rewrite_pkgconfig + basepath = @formula.prefix.realpath + pc_files = %w[lib share].flat_map do |subdir| + pc_dir = basepath/subdir/"pkgconfig" + next [] if !pc_dir.exist? || @formula.skip_clean?(basepath/subdir) || @formula.skip_clean?(pc_dir) + + pc_dir.glob("*.pc").reject { |pc_file| @formula.skip_clean?(pc_file) } + end + return if pc_files.empty? + + # TODO: Add support for `brew unlink`-ed formulae and check on recursive dependencies + deps_pc_files = @formula.deps + .filter_map { |dep| dep.to_formula if !dep.build? && !dep.test? } + .select(&:keg_only?) + .flat_map { |f| f.opt_prefix.glob("{lib,share}/pkgconfig/*.pc") } + .to_h { |pc_file| [pc_file.basename(".pc").to_s.downcase, pc_file.to_s] } + deps_pc_modules_pattern = deps_pc_files.keys.map { |mod| Regexp.escape(mod) }.join("|") + + pc_files.each do |pc_file| + modified_lines = pc_file.each_line.map do |line| + rewrote_prefix = line.gsub!(@formula.prefix.realpath.to_s, @formula.opt_prefix.to_s).present? + next [line, rewrote_prefix] if deps_pc_files.empty? || !line.start_with?(/Requires(?:\.private)?:/) + + # pkgconf's pc.5 man page defines dependency list ABNF syntax as: + # + # > package-list = *WSP *( package-spec *( package-sep ) ) + # > package-sep = WSP / "," + # > package-spec = package-key [ ver-op package-version ] + # > ver-op = "<" / "<=" / "=" / "!=" / ">=" / ">" + # + # A simplified regular expression is used to lookahead/lookbehind for common + # separator characters to extract the modules used in Requires/Requires.private + rewrote_module = line.gsub!(/(?<=[:,\s])(#{deps_pc_modules_pattern})(?=[<=>!,\s])/io, deps_pc_files).present? + [line, rewrote_prefix || rewrote_module] + end + next if modified_lines.none?(&:second) + + pc_file.atomic_write(modified_lines.map(&:first).join) + end + end + sig { void } def rewrite_shebangs require "language/node" diff --git a/Library/Homebrew/test/cleaner_spec.rb b/Library/Homebrew/test/cleaner_spec.rb index f5f51a29418b0..c6bc63280c3b0 100644 --- a/Library/Homebrew/test/cleaner_spec.rb +++ b/Library/Homebrew/test/cleaner_spec.rb @@ -201,6 +201,50 @@ expect(file.read).to eq "brew\n" end + + it "modifies Cellar prefix in pkg-config files" do + file = f.lib/"pkgconfig/test.pc" + file.dirname.mkpath + file.write <<~EOS + prefix=#{f.prefix} + includedir=#{f.include} + libdir=#{f.lib} + Name: test + Description: test module + Version: 1.2.3 + Cflags: -I${includedir} + EOS + + cleaner.clean + + expect(file.read).to eq <<~EOS + prefix=#{f.opt_prefix} + includedir=#{f.opt_include} + libdir=#{f.opt_lib} + Name: test + Description: test module + Version: 1.2.3 + Cflags: -I${includedir} + EOS + end + + it "does not modify pkg-config files with no rewrites" do + file = f.lib/"pkgconfig/test.pc" + file.dirname.mkpath + file.write <<~EOS + prefix=#{f.opt_prefix} + includedir=${prefix}/include + Name: test + Description: test module + Version: 1.2.3 + Cflags: -I${includedir} + EOS + start_mtime = file.mtime + + cleaner.clean + + expect(file.mtime).to eq start_mtime + end end describe "::skip_clean" do