prebake 0.2.10 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/prebake/async_publisher.rb +6 -0
- data/lib/prebake/elf_inspector.rb +35 -11
- data/lib/prebake/ext_builder_patch.rb +15 -14
- data/lib/prebake/portability_guard.rb +38 -0
- data/lib/prebake/static_ruby.rb +42 -0
- data/lib/prebake.rb +6 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9cf794eed23b32317c56fd2fb39f3bce0d7d38d41cbfc1074f4ee16d2f7ee377
|
|
4
|
+
data.tar.gz: b10916ab61bb14d229ad357ec55c9bbefcf0ce8063f0216d897d26de962db2a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 14dc69e465885f018ef9018989a2aa0fcff42f17a65d8b05cebb31c78a5d33db08bf3b52245a3add4f484428834410501d2c710bb00ab1df76f6ffaa72920b0b
|
|
7
|
+
data.tar.gz: 601810a58b81f7d89f4a44c8dbb337faa759ba399cc9b0252b5bd4de12dabba78f8ebbc470becdb3cbed686fecb740155591275f27a7af4a9a7d4b4c7e901237
|
|
@@ -75,6 +75,12 @@ module Prebake
|
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
if !Prebake.libruby_available? && ElfInspector.libruby_needed_for_gem?(gem_path)
|
|
79
|
+
Logger.warn "Skipping push of #{cache_key}: binary requires libruby.so (dynamic Ruby) but this is a static Ruby build; binary would crash on this platform"
|
|
80
|
+
FileUtils.rm_f(gem_path)
|
|
81
|
+
return nil
|
|
82
|
+
end
|
|
83
|
+
|
|
78
84
|
Logger.debug "Built #{cache_key}"
|
|
79
85
|
[gem_path, cache_key, checksum, backend]
|
|
80
86
|
rescue StandardError => e
|
|
@@ -9,17 +9,9 @@ require_relative "logger"
|
|
|
9
9
|
module Prebake
|
|
10
10
|
module ElfInspector
|
|
11
11
|
def self.required_glibc_for_gem(gem_path)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
next if File.symlink?(binary) || File.size(binary).zero?
|
|
16
|
-
|
|
17
|
-
required_glibc(binary)
|
|
18
|
-
end
|
|
19
|
-
return nil if versions.empty?
|
|
20
|
-
|
|
21
|
-
versions.max_by { |v| Gem::Version.new(v) }
|
|
22
|
-
end
|
|
12
|
+
versions = []
|
|
13
|
+
each_gem_binary(gem_path) { |binary| v = required_glibc(binary); versions << v if v }
|
|
14
|
+
versions.empty? ? nil : versions.max_by { |v| Gem::Version.new(v) }
|
|
23
15
|
rescue StandardError => e
|
|
24
16
|
# Malformed gem, missing objdump, or I/O error — treat as unknown, let
|
|
25
17
|
# downstream extraction catch real corruption.
|
|
@@ -43,6 +35,27 @@ module Prebake
|
|
|
43
35
|
versions.max_by { |v| Gem::Version.new(v) }
|
|
44
36
|
end
|
|
45
37
|
|
|
38
|
+
def self.libruby_needed_for_gem?(gem_path)
|
|
39
|
+
each_gem_binary(gem_path) { |binary| return true if libruby_needed?(binary) }
|
|
40
|
+
false
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
Logger.debug "libruby inspection failed for #{File.basename(gem_path)}: #{e.message}"
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.libruby_needed?(path)
|
|
47
|
+
needed_libraries(path).any? { |lib| lib.start_with?("libruby") }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.needed_libraries(path)
|
|
51
|
+
out, status = Open3.capture2e("objdump", "-p", path)
|
|
52
|
+
return [] unless status.success?
|
|
53
|
+
|
|
54
|
+
out.scan(/NEEDED\s+(\S+)/).flatten
|
|
55
|
+
rescue Errno::ENOENT
|
|
56
|
+
[]
|
|
57
|
+
end
|
|
58
|
+
|
|
46
59
|
def self.run_objdump(path)
|
|
47
60
|
out, status = Open3.capture2e("objdump", "-T", path)
|
|
48
61
|
return nil unless status.success?
|
|
@@ -51,5 +64,16 @@ module Prebake
|
|
|
51
64
|
rescue Errno::ENOENT
|
|
52
65
|
nil
|
|
53
66
|
end
|
|
67
|
+
|
|
68
|
+
private_class_method def self.each_gem_binary(gem_path)
|
|
69
|
+
Dir.mktmpdir("prebake-gem") do |tmpdir|
|
|
70
|
+
Gem::Package.new(gem_path).extract_files(tmpdir)
|
|
71
|
+
Dir.glob(File.join(tmpdir, "**/*.{so,bundle,dll}")).each do |binary|
|
|
72
|
+
next if File.symlink?(binary) || File.size(binary).zero?
|
|
73
|
+
|
|
74
|
+
yield binary
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
54
78
|
end
|
|
55
79
|
end
|
|
@@ -6,8 +6,7 @@ require "digest"
|
|
|
6
6
|
require_relative "cache_key"
|
|
7
7
|
require_relative "platform"
|
|
8
8
|
require_relative "extractor"
|
|
9
|
-
require_relative "
|
|
10
|
-
require_relative "glibc"
|
|
9
|
+
require_relative "portability_guard"
|
|
11
10
|
require_relative "logger"
|
|
12
11
|
|
|
13
12
|
module Prebake
|
|
@@ -17,6 +16,12 @@ module Prebake
|
|
|
17
16
|
return super unless Prebake.enabled?
|
|
18
17
|
return super unless Prebake.backend # nil if config failed
|
|
19
18
|
|
|
19
|
+
if Prebake.optional_native_extension?(@spec.name) && !Prebake.libruby_available?
|
|
20
|
+
Logger.warn "#{@spec.name}: native extension skipped (optional gem, libruby.so absent on static Ruby build)"
|
|
21
|
+
install_without_native_extension
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
20
25
|
platform = Platform.generalized
|
|
21
26
|
cache_key = CacheKey.for(@spec.name, @spec.version.to_s, platform)
|
|
22
27
|
|
|
@@ -40,7 +45,7 @@ module Prebake
|
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
if verify_checksum(cache_key, expected_checksum, cached_gem)
|
|
43
|
-
unless portable_for_host?(cached_gem)
|
|
48
|
+
unless PortabilityGuard.portable_for_host?(cached_gem, spec_name: @spec.name)
|
|
44
49
|
# Binary is valid for other hosts; don't delete from backend.
|
|
45
50
|
return super
|
|
46
51
|
end
|
|
@@ -85,16 +90,8 @@ module Prebake
|
|
|
85
90
|
end
|
|
86
91
|
end
|
|
87
92
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
return true unless Glibc.linux?
|
|
91
|
-
return true if Prebake.skip_portability_check?
|
|
92
|
-
|
|
93
|
-
required = ElfInspector.required_glibc_for_gem(gem_path)
|
|
94
|
-
return true if Glibc.compatible?(required)
|
|
95
|
-
|
|
96
|
-
Logger.warn "Cached #{@spec.name} requires glibc #{required}, host has #{Glibc.detected_version || 'unknown'}; falling back to source build"
|
|
97
|
-
false
|
|
93
|
+
def install_without_native_extension
|
|
94
|
+
mark_build_complete
|
|
98
95
|
end
|
|
99
96
|
|
|
100
97
|
def install_from_cache(gem_path)
|
|
@@ -105,9 +102,13 @@ module Prebake
|
|
|
105
102
|
return false
|
|
106
103
|
end
|
|
107
104
|
|
|
105
|
+
mark_build_complete
|
|
106
|
+
true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def mark_build_complete
|
|
108
110
|
FileUtils.mkdir_p(File.dirname(@spec.gem_build_complete_path))
|
|
109
111
|
FileUtils.touch(@spec.gem_build_complete_path)
|
|
110
|
-
true
|
|
111
112
|
end
|
|
112
113
|
end
|
|
113
114
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "elf_inspector"
|
|
4
|
+
require_relative "glibc"
|
|
5
|
+
require_relative "logger"
|
|
6
|
+
|
|
7
|
+
module Prebake
|
|
8
|
+
# Host-side portability guards for cached gems: ensures the cached binary
|
|
9
|
+
# will actually load on this host (glibc version + libruby.so presence).
|
|
10
|
+
module PortabilityGuard
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def portable_for_host?(gem_path, spec_name:)
|
|
14
|
+
return true unless Glibc.linux?
|
|
15
|
+
return true if Prebake.skip_portability_check?
|
|
16
|
+
|
|
17
|
+
glibc_ok?(gem_path, spec_name:) && libruby_ok?(gem_path, spec_name:)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def glibc_ok?(gem_path, spec_name:)
|
|
21
|
+
required = ElfInspector.required_glibc_for_gem(gem_path)
|
|
22
|
+
return true if Glibc.compatible?(required)
|
|
23
|
+
|
|
24
|
+
Logger.warn "Cached #{spec_name} requires glibc #{required}, " \
|
|
25
|
+
"host has #{Glibc.detected_version || 'unknown'}; falling back to source build"
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def libruby_ok?(gem_path, spec_name:)
|
|
30
|
+
return true if Prebake.libruby_available?
|
|
31
|
+
return true unless ElfInspector.libruby_needed_for_gem?(gem_path)
|
|
32
|
+
|
|
33
|
+
Logger.warn "Cached #{spec_name} requires libruby.so (dynamic Ruby build) " \
|
|
34
|
+
"but this host has a static Ruby; falling back to source build"
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prebake
|
|
4
|
+
# Detection and configuration for static Ruby builds (e.g. Paketo MRI buildpack),
|
|
5
|
+
# where libruby.so is absent and native extensions dynamically linked against it
|
|
6
|
+
# would crash at load time.
|
|
7
|
+
module StaticRuby
|
|
8
|
+
# Gems whose native extensions are entirely optional — the gem runs correctly in pure Ruby
|
|
9
|
+
# mode when the extension can't be loaded. Prebake skips the extension for these gems
|
|
10
|
+
# instead of installing a broken .so. Extend via PREBAKE_OPTIONAL_NATIVE_EXTENSIONS=gem1,gem2.
|
|
11
|
+
# Intentionally empty: bootsnap 1.18+ requires its native extension unconditionally;
|
|
12
|
+
# skipping the build produces a broken install.
|
|
13
|
+
OPTIONAL_NATIVE_EXTENSIONS_DEFAULT = %w[].freeze
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def optional_native_extensions
|
|
17
|
+
@optional_native_extensions ||= begin
|
|
18
|
+
extra = ENV.fetch("PREBAKE_OPTIONAL_NATIVE_EXTENSIONS", "")
|
|
19
|
+
.split(",").map(&:strip).reject(&:empty?)
|
|
20
|
+
(OPTIONAL_NATIVE_EXTENSIONS_DEFAULT + extra).uniq
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def optional_native_extension?(gem_name)
|
|
25
|
+
optional_native_extensions.include?(gem_name)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def libruby_available?
|
|
29
|
+
return @libruby_available if defined?(@libruby_available)
|
|
30
|
+
|
|
31
|
+
libruby_so = RbConfig::CONFIG["LIBRUBY_SO"]
|
|
32
|
+
@libruby_available = !libruby_so.nil? && !libruby_so.empty? &&
|
|
33
|
+
File.exist?(File.join(RbConfig::CONFIG["libdir"], libruby_so))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reset!
|
|
37
|
+
remove_instance_variable(:@optional_native_extensions) if defined?(@optional_native_extensions)
|
|
38
|
+
remove_instance_variable(:@libruby_available) if defined?(@libruby_available)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/prebake.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "prebake/logger"
|
|
4
|
+
require_relative "prebake/static_ruby"
|
|
4
5
|
|
|
5
6
|
module Prebake
|
|
6
7
|
class Error < StandardError; end
|
|
@@ -27,6 +28,10 @@ module Prebake
|
|
|
27
28
|
ENV.fetch("PREBAKE_MAX_GLIBC", nil)
|
|
28
29
|
end
|
|
29
30
|
|
|
31
|
+
def self.optional_native_extensions = StaticRuby.optional_native_extensions
|
|
32
|
+
def self.optional_native_extension?(gem_name) = StaticRuby.optional_native_extension?(gem_name)
|
|
33
|
+
def self.libruby_available? = StaticRuby.libruby_available?
|
|
34
|
+
|
|
30
35
|
def self.backend
|
|
31
36
|
return @backend if defined?(@backend_loaded)
|
|
32
37
|
|
|
@@ -49,6 +54,7 @@ module Prebake
|
|
|
49
54
|
def self.reset!
|
|
50
55
|
remove_instance_variable(:@backend_loaded) if defined?(@backend_loaded)
|
|
51
56
|
remove_instance_variable(:@backend) if defined?(@backend)
|
|
57
|
+
StaticRuby.reset!
|
|
52
58
|
end
|
|
53
59
|
|
|
54
60
|
def self.setup!
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prebake
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thejus Paul
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Prebake speeds up bundle install by skipping native gem compilation.
|
|
14
14
|
It fetches precompiled binaries for gems like puma, nokogiri, pg, grpc, and bootsnap
|
|
@@ -40,6 +40,8 @@ files:
|
|
|
40
40
|
- lib/prebake/logger.rb
|
|
41
41
|
- lib/prebake/platform.rb
|
|
42
42
|
- lib/prebake/platform_gem_builder.rb
|
|
43
|
+
- lib/prebake/portability_guard.rb
|
|
44
|
+
- lib/prebake/static_ruby.rb
|
|
43
45
|
- plugins.rb
|
|
44
46
|
homepage: https://github.com/gembakery/prebake
|
|
45
47
|
licenses:
|