Skip to content
Merged
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
2 changes: 1 addition & 1 deletion lib/prebake/async_publisher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def self.enqueue(spec, backend)
@mutex.synchronize { @pending_specs << [spec, backend] }
end

def self.wait_for_completion(timeout: 120)
def self.publish_pending(timeout: 120)
specs = @mutex.synchronize { @pending_specs.dup }
return if specs.empty?

Expand Down
8 changes: 4 additions & 4 deletions lib/prebake/elf_inspector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ def self.libruby_needed?(path)
end

def self.needed_libraries(path)
out, status = Open3.capture2e("objdump", "-p", path)
output, status = Open3.capture2e("objdump", "-p", path)
return [] unless status.success?

out.scan(/NEEDED\s+(\S+)/).flatten
output.scan(/NEEDED\s+(\S+)/).flatten
rescue Errno::ENOENT
[]
end

def self.run_objdump(path)
out, status = Open3.capture2e("objdump", "-T", path)
output, status = Open3.capture2e("objdump", "-T", path)
return nil unless status.success?

out
output
rescue Errno::ENOENT
nil
end
Expand Down
14 changes: 5 additions & 9 deletions lib/prebake/ext_builder_patch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def build_extensions

if Prebake.optional_native_extension?(@spec.name) && !Prebake.libruby_available?
Logger.warn "#{@spec.name}: native extension skipped (optional gem, libruby.so absent on static Ruby build)"
install_without_native_extension
mark_build_complete
return
end

Expand All @@ -53,7 +53,7 @@ def build_extensions
return super
end

if verify_checksum(cache_key, expected_checksum, cached_gem)
if checksum_matches?(cache_key, expected_checksum, cached_gem)
unless PortabilityGuard.portable_for_host?(cached_gem, spec_name: @spec.name)
# Binary is valid for other hosts; don't delete from backend.
return super
Expand Down Expand Up @@ -85,11 +85,11 @@ def trigger_rebuild(cache_key)
Logger.warn "No checksum available for #{cache_key}, removing cached gem"
Prebake.backend.delete(cache_key)
# GET triggers the worker to dispatch a rebuild via GH Actions on cache miss
trigger_path = Prebake.backend.fetch(cache_key)
FileUtils.rm_f(trigger_path) if trigger_path
leftover = Prebake.backend.fetch(cache_key)
FileUtils.rm_f(leftover) if leftover
end

def verify_checksum(cache_key, expected, gem_path)
def checksum_matches?(cache_key, expected, gem_path)
return true if expected.nil?

actual = Digest::SHA256.file(gem_path).hexdigest
Expand All @@ -101,10 +101,6 @@ def verify_checksum(cache_key, expected, gem_path)
end
end

def install_without_native_extension
mark_build_complete
end

def install_from_cache(gem_path)
count = Extractor.install(gem_path, @spec)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
require_relative "logger"

module Prebake
module ExtensionValidator
module ExtensionLayout
BINARY_GLOB = "*.{so,bundle,dll}"

def self.validate_all
Bundler.definition.specs.each { |spec| validate(spec) }
def self.repair_all
Bundler.definition.specs.each { |spec| repair(spec) }
end

def self.validate(spec)
def self.repair(spec)
ext_dir = spec.extension_dir
return unless File.exist?(File.join(ext_dir, ".prebake"))

Expand All @@ -30,7 +30,7 @@ def self.validate(spec)
next if File.exist?(dest)

FileUtils.cp(binary, dest)
Logger.info "Validator: copied #{File.basename(binary)} to #{ext_dir}"
Logger.info "Restored #{File.basename(binary)} to #{ext_dir}"
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/prebake/extractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def self.install(gem_path, spec)
end
end

# Mark this extension_dir as prebake-managed for post-install validation
# Mark this extension_dir as prebake-managed for post-install layout repair
FileUtils.touch(File.join(spec.extension_dir, ".prebake")) if extracted_count.positive?

Logger.info "Installed precompiled #{File.basename(gem_path)} " \
Expand Down
4 changes: 2 additions & 2 deletions lib/prebake/glibc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ def self.reset!
end

def self.run_ldd
out, status = Open3.capture2e("ldd", "--version")
output, status = Open3.capture2e("ldd", "--version")
return nil unless status.success?

out
output
rescue Errno::ENOENT
nil
end
Expand Down
6 changes: 3 additions & 3 deletions lib/prebake/hooks.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require_relative "async_publisher"
require_relative "extension_validator"
require_relative "extension_layout"
require_relative "logger"

module Prebake
Expand All @@ -22,11 +22,11 @@ def self.register!
end

Bundler::Plugin::API.hook("after-install-all") do |_deps|
ExtensionValidator.validate_all
ExtensionLayout.repair_all

next unless Prebake.push_enabled?

AsyncPublisher.wait_for_completion
AsyncPublisher.publish_pending
end

Logger.debug "Hooks registered"
Expand Down
6 changes: 3 additions & 3 deletions lib/prebake/platform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ module Platform
[/\Aaarch64-linux(-gnu)?\z/, "aarch64-linux"]
].freeze

def self.normalize(platform_string)
def self.normalize(platform)
NORMALIZATIONS.each do |pattern, normalized|
return normalized if platform_string.match?(pattern)
return normalized if platform.match?(pattern)
end

platform_string
platform
end

def self.generalized
Expand Down
4 changes: 2 additions & 2 deletions lib/prebake/static_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ module StaticRuby
# instead of installing a broken .so. Extend via PREBAKE_OPTIONAL_NATIVE_EXTENSIONS=gem1,gem2.
# Intentionally empty: bootsnap 1.18+ requires its native extension unconditionally;
# skipping the build produces a broken install.
OPTIONAL_NATIVE_EXTENSIONS_DEFAULT = %w[].freeze
DEFAULT_OPTIONAL_NATIVE_EXTENSIONS = %w[].freeze

class << self
def optional_native_extensions
@optional_native_extensions ||= begin
extra = ENV.fetch("PREBAKE_OPTIONAL_NATIVE_EXTENSIONS", "")
.split(",").map(&:strip).reject(&:empty?)
(OPTIONAL_NATIVE_EXTENSIONS_DEFAULT + extra).uniq
(DEFAULT_OPTIONAL_NATIVE_EXTENSIONS + extra).uniq
end
end

Expand Down
44 changes: 22 additions & 22 deletions test/async_publisher_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def teardown
FileUtils.rm_rf(@tmpdir)
end

def mock_spec(name, version, build_args: [])
def build_spec(name, version, build_args: [])
stub(name:, version: Gem::Version.new(version), build_args:)
end

Expand All @@ -25,8 +25,8 @@ def temp_gem_path(name = "fake")
path
end

def test_enqueue_and_wait_pushes_gem
spec = mock_spec("testgem", "1.0.0")
def test_enqueue_and_publish_pushes_gem
spec = build_spec("testgem", "1.0.0")
gem_path = temp_gem_path
pushed = []
backend = stub(exists?: false, push: -> { pushed << true })
Expand All @@ -36,26 +36,26 @@ def test_enqueue_and_wait_pushes_gem
Prebake::PlatformGemBuilder.stubs(:new).with(spec).returns(builder)

Prebake::AsyncPublisher.enqueue(spec, backend)
Prebake::AsyncPublisher.wait_for_completion(timeout: 5)
Prebake::AsyncPublisher.publish_pending(timeout: 5)
end

def test_wait_with_no_enqueues_does_nothing
Prebake::AsyncPublisher.wait_for_completion(timeout: 1)
def test_publish_with_no_enqueues_does_nothing
Prebake::AsyncPublisher.publish_pending(timeout: 1)
end

def test_errors_in_build_are_rescued
spec = mock_spec("badgem", "0.1.0")
spec = build_spec("badgem", "0.1.0")
backend = stub
backend.stubs(:exists?).raises(StandardError, "boom")
Prebake::Logger.stubs(:warn)

Prebake::AsyncPublisher.enqueue(spec, backend)
Prebake::AsyncPublisher.wait_for_completion(timeout: 5)
Prebake::AsyncPublisher.publish_pending(timeout: 5)
end

def test_skips_push_when_built_gem_exceeds_max_glibc
ENV["PREBAKE_MAX_GLIBC"] = "2.28"
spec = mock_spec("testgem", "1.0.0")
spec = build_spec("testgem", "1.0.0")
gem_path = temp_gem_path
backend = stub(exists?: false)
backend.expects(:push).never
Expand All @@ -65,14 +65,14 @@ def test_skips_push_when_built_gem_exceeds_max_glibc
Prebake::ElfInspector.stubs(:required_glibc_for_gem).with(gem_path).returns("2.35")

Prebake::AsyncPublisher.enqueue(spec, backend)
Prebake::AsyncPublisher.wait_for_completion(timeout: 5)
Prebake::AsyncPublisher.publish_pending(timeout: 5)
ensure
ENV.delete("PREBAKE_MAX_GLIBC")
end

def test_pushes_when_built_gem_within_max_glibc
ENV["PREBAKE_MAX_GLIBC"] = "2.35"
spec = mock_spec("testgem", "1.0.0")
spec = build_spec("testgem", "1.0.0")
gem_path = temp_gem_path
backend = stub(exists?: false)
backend.expects(:push).with(gem_path, anything, "sha")
Expand All @@ -82,13 +82,13 @@ def test_pushes_when_built_gem_within_max_glibc
Prebake::ElfInspector.stubs(:required_glibc_for_gem).with(gem_path).returns("2.28")

Prebake::AsyncPublisher.enqueue(spec, backend)
Prebake::AsyncPublisher.wait_for_completion(timeout: 5)
Prebake::AsyncPublisher.publish_pending(timeout: 5)
ensure
ENV.delete("PREBAKE_MAX_GLIBC")
end

def test_pushes_without_check_when_max_glibc_unset
spec = mock_spec("testgem", "1.0.0")
spec = build_spec("testgem", "1.0.0")
gem_path = temp_gem_path
backend = stub(exists?: false)
backend.expects(:push).with(gem_path, anything, "sha")
Expand All @@ -98,23 +98,23 @@ def test_pushes_without_check_when_max_glibc_unset
Prebake::ElfInspector.expects(:required_glibc_for_gem).never

Prebake::AsyncPublisher.enqueue(spec, backend)
Prebake::AsyncPublisher.wait_for_completion(timeout: 5)
Prebake::AsyncPublisher.publish_pending(timeout: 5)
end

def test_skips_push_when_gem_was_compiled_with_build_options
spec = mock_spec("pg", "1.5.4", build_args: ["--with-pg-config=/opt/pg/bin/pg_config"])
spec = build_spec("pg", "1.5.4", build_args: ["--with-pg-config=/opt/pg/bin/pg_config"])
backend = stub(exists?: false)
backend.expects(:push).never

builder = stub(build: temp_gem_path, checksum: "sha")
Prebake::PlatformGemBuilder.stubs(:new).with(spec).returns(builder)

Prebake::AsyncPublisher.enqueue(spec, backend)
Prebake::AsyncPublisher.wait_for_completion(timeout: 5)
Prebake::AsyncPublisher.publish_pending(timeout: 5)
end

def test_skips_push_when_built_gem_needs_libruby_on_static_ruby
spec = mock_spec("bootsnap", "1.23.0")
spec = build_spec("bootsnap", "1.23.0")
gem_path = temp_gem_path
backend = stub(exists?: false)
backend.expects(:push).never
Expand All @@ -125,13 +125,13 @@ def test_skips_push_when_built_gem_needs_libruby_on_static_ruby
Prebake::ElfInspector.stubs(:libruby_needed_for_gem?).with(gem_path).returns(true)

Prebake::AsyncPublisher.enqueue(spec, backend)
Prebake::AsyncPublisher.wait_for_completion(timeout: 5)
Prebake::AsyncPublisher.publish_pending(timeout: 5)

refute File.exist?(gem_path), "broken gem file should be cleaned up"
end

def test_pushes_when_libruby_needed_but_available
spec = mock_spec("bootsnap", "1.23.0")
spec = build_spec("bootsnap", "1.23.0")
gem_path = temp_gem_path
backend = stub(exists?: false)
backend.expects(:push).with(gem_path, anything, "sha")
Expand All @@ -142,13 +142,13 @@ def test_pushes_when_libruby_needed_but_available
Prebake::ElfInspector.expects(:libruby_needed_for_gem?).never

Prebake::AsyncPublisher.enqueue(spec, backend)
Prebake::AsyncPublisher.wait_for_completion(timeout: 5)
Prebake::AsyncPublisher.publish_pending(timeout: 5)
end

def test_multiple_enqueues_all_processed
pushed = []
3.times do |i|
spec = mock_spec("gem#{i}", "1.0.#{i}")
spec = build_spec("gem#{i}", "1.0.#{i}")
gem_path = temp_gem_path("gem#{i}")
backend = stub(exists?: false)
backend.stubs(:push).with { pushed << i }
Expand All @@ -159,7 +159,7 @@ def test_multiple_enqueues_all_processed
Prebake::AsyncPublisher.enqueue(spec, backend)
end

Prebake::AsyncPublisher.wait_for_completion(timeout: 10)
Prebake::AsyncPublisher.publish_pending(timeout: 10)
assert_equal 3, pushed.size
end
end
Loading
Loading