From ba387071d6989166b380095240efb8f570386a30 Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Sat, 8 Aug 2026 15:45:47 +0200 Subject: [PATCH] Stop shipping triplicate Linux shared libraries in native gems MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linux SONAME layout is one real .so plus short-name symlinks (libfoo.so / libfoo.so.0 → libfoo.so.X.Y.Z). package:linux used to materialize those symlinks as full file copies because gems don't preserve symlinks, tripling harfbuzz/plutobook payload (~14MB waste). Keep a single real binary per library, record the short names in so_aliases, and recreate relative symlinks in extconf at gem install so -lplutobook and runtime DT_NEEDED still resolve. --- ext/page_print/extconf.rb | 19 +++++++++++++++++++ rakelib/package_linux.rake | 32 ++++++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/ext/page_print/extconf.rb b/ext/page_print/extconf.rb index 16a1a2f..8c0a5f4 100644 --- a/ext/page_print/extconf.rb +++ b/ext/page_print/extconf.rb @@ -12,6 +12,24 @@ def page_print_native_platform end end +def restore_vendor_so_aliases(vendor_lib_dir) + manifest_path = File.join(vendor_lib_dir, 'so_aliases') + return unless File.file?(manifest_path) + + File.foreach(manifest_path) do |line| + alias_name, target_name = line.strip.split("\t", 2) + next if alias_name.nil? || alias_name.empty? || target_name.nil? || target_name.empty? + next if alias_name.include?('/') || target_name.include?('/') || alias_name.start_with?('.') || target_name.start_with?('.') + + alias_path = File.join(vendor_lib_dir, alias_name) + target_path = File.join(vendor_lib_dir, target_name) + next unless File.file?(target_path) + next if File.exist?(alias_path) || File.symlink?(alias_path) + + File.symlink(target_name, alias_path) + end +end + project_root = File.expand_path('../..', __dir__) vendor_platform = ENV.fetch('PAGE_PRINT_VENDOR_PLATFORM', page_print_native_platform) vendor_dir = File.join(project_root, 'lib', 'page_print', 'vendor', vendor_platform) @@ -19,6 +37,7 @@ def page_print_native_platform vendor_lib_dir = File.join(vendor_dir, 'lib') if File.directory?(vendor_include_dir) && File.directory?(vendor_lib_dir) + restore_vendor_so_aliases(vendor_lib_dir) dir_config('plutobook', vendor_include_dir, vendor_lib_dir) if vendor_platform.end_with?('-linux') diff --git a/rakelib/package_linux.rake b/rakelib/package_linux.rake index 5278db4..d3e335e 100644 --- a/rakelib/package_linux.rake +++ b/rakelib/package_linux.rake @@ -45,7 +45,6 @@ namespace :package do FileUtils.mkdir_p(vendor_dir) FileUtils.cp_r(File.join(plutobook_install_dir, "include"), vendor_dir) FileUtils.cp_r(File.join(plutobook_install_dir, "lib"), vendor_dir) - materialize_vendor_symlinks(File.join(vendor_dir, "lib")) ENV["PAGE_PRINT_VENDOR_PLATFORM"] = PAGE_PRINT_LINUX_PLATFORM Rake::Task["clean"].invoke if Rake::Task.task_defined?("clean") @@ -56,6 +55,7 @@ namespace :package do vendor_linux_shared_libraries(extension_path, vendor_lib_dir) patch_linux_rpaths(extension_path, vendor_lib_dir) verify_no_missing_linux_libraries(extension_path, vendor_lib_dir) + collapse_vendor_shared_library_aliases(vendor_lib_dir) FileUtils.rm_f(extension_path) Bundler.with_unbundled_env do @@ -98,14 +98,25 @@ def vendor_linux_shared_libraries(extension_path, vendor_lib_dir) end end -def materialize_vendor_symlinks(vendor_lib_dir) +def collapse_vendor_shared_library_aliases(vendor_lib_dir) + aliases = {} + Dir.glob(File.join(vendor_lib_dir, "*.so*")).each do |library_path| next unless File.symlink?(library_path) - real_path = File.realpath(library_path) + target_name = File.basename(File.realpath(library_path)) + alias_name = File.basename(library_path) + next if alias_name == target_name + + aliases[alias_name] = target_name FileUtils.rm(library_path) - File.binwrite(library_path, File.binread(real_path)) - FileUtils.chmod(0o755, library_path) + end + + manifest_path = File.join(vendor_lib_dir, "so_aliases") + if aliases.empty? + FileUtils.rm_f(manifest_path) + else + File.write(manifest_path, aliases.sort.map { |alias_name, target_name| "#{alias_name}\t#{target_name}\n" }.join) end end @@ -113,12 +124,21 @@ def patch_linux_rpaths(extension_path, vendor_lib_dir) sh "patchelf --set-rpath '$ORIGIN/vendor/#{package_linux_platform}/lib' #{extension_path.shellescape}" Dir.glob(File.join(vendor_lib_dir, "*.so*")).each do |library_path| + next if File.symlink?(library_path) + sh "patchelf --set-rpath '$ORIGIN' #{library_path.shellescape}" end end def verify_no_missing_linux_libraries(extension_path, vendor_lib_dir) - [ extension_path, *Dir.glob(File.join(vendor_lib_dir, "*.so*")) ].each do |binary| + binaries = [extension_path] + Dir.glob(File.join(vendor_lib_dir, "*.so*")).each do |library_path| + next if File.symlink?(library_path) + + binaries << library_path + end + + binaries.each do |binary| output = `ldd #{binary.shellescape}` abort "Missing shared library for #{binary}:\n#{output}" if output.include?("not found") end