prebake 0.2.8 → 0.2.10
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 +10 -0
- data/lib/prebake/elf_inspector.rb +55 -0
- data/lib/prebake/ext_builder_patch.rb +19 -0
- data/lib/prebake/extension_validator.rb +37 -0
- data/lib/prebake/extractor.rb +3 -0
- data/lib/prebake/glibc.rb +48 -0
- data/lib/prebake/hooks.rb +3 -0
- data/lib/prebake/logger.rb +2 -2
- data/lib/prebake.rb +8 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5016083674e522c23d95fa0526df6b35f159e529b0d3116fc24daf70e1c52df9
|
|
4
|
+
data.tar.gz: 7a32973b781cb342998d150c5fe75b944c4865055df2f168c2bebeb48fa1450c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9ea7f985f4ca767edaca8598a79023e0ec9c0fe1ea9e5219a368ce4d44e4d0ef8ccb8bf8a57434fce514afc9e80694a4a72c3e2335d54ba37d2857a8d43476d
|
|
7
|
+
data.tar.gz: 05adfe955164c50037842cd380504e938df4618160de34c9fee7e7e25e225226eefb44a4a9fc6a2181f41702e23ff2ed00686bffaee20643de811cde9d98922a
|
|
@@ -4,6 +4,7 @@ require "fileutils"
|
|
|
4
4
|
require_relative "platform_gem_builder"
|
|
5
5
|
require_relative "cache_key"
|
|
6
6
|
require_relative "platform"
|
|
7
|
+
require_relative "elf_inspector"
|
|
7
8
|
require_relative "logger"
|
|
8
9
|
|
|
9
10
|
module Prebake
|
|
@@ -65,6 +66,15 @@ module Prebake
|
|
|
65
66
|
gem_path = builder.build
|
|
66
67
|
checksum = builder.checksum
|
|
67
68
|
|
|
69
|
+
if (max = Prebake.max_glibc)
|
|
70
|
+
required = ElfInspector.required_glibc_for_gem(gem_path)
|
|
71
|
+
if required && Gem::Version.new(required) > Gem::Version.new(max)
|
|
72
|
+
Logger.warn "Skipping push of #{cache_key}: requires glibc #{required} (> PREBAKE_MAX_GLIBC=#{max})"
|
|
73
|
+
FileUtils.rm_f(gem_path)
|
|
74
|
+
return nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
68
78
|
Logger.debug "Built #{cache_key}"
|
|
69
79
|
[gem_path, cache_key, checksum, backend]
|
|
70
80
|
rescue StandardError => e
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
require "rubygems/package"
|
|
7
|
+
require_relative "logger"
|
|
8
|
+
|
|
9
|
+
module Prebake
|
|
10
|
+
module ElfInspector
|
|
11
|
+
def self.required_glibc_for_gem(gem_path)
|
|
12
|
+
Dir.mktmpdir("prebake-portability") do |tmpdir|
|
|
13
|
+
Gem::Package.new(gem_path).extract_files(tmpdir)
|
|
14
|
+
versions = Dir.glob(File.join(tmpdir, "**/*.{so,bundle,dll}")).filter_map do |binary|
|
|
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
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
# Malformed gem, missing objdump, or I/O error — treat as unknown, let
|
|
25
|
+
# downstream extraction catch real corruption.
|
|
26
|
+
Logger.debug "Portability inspection failed for #{File.basename(gem_path)}: #{e.message}"
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.required_glibc(path)
|
|
31
|
+
return nil unless File.exist?(path)
|
|
32
|
+
|
|
33
|
+
output = run_objdump(path)
|
|
34
|
+
return nil if output.nil? || output.empty?
|
|
35
|
+
|
|
36
|
+
parse_glibc_version(output)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.parse_glibc_version(output)
|
|
40
|
+
versions = output.scan(/GLIBC_(\d+(?:\.\d+)+)/).flatten
|
|
41
|
+
return nil if versions.empty?
|
|
42
|
+
|
|
43
|
+
versions.max_by { |v| Gem::Version.new(v) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.run_objdump(path)
|
|
47
|
+
out, status = Open3.capture2e("objdump", "-T", path)
|
|
48
|
+
return nil unless status.success?
|
|
49
|
+
|
|
50
|
+
out
|
|
51
|
+
rescue Errno::ENOENT
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -6,6 +6,8 @@ require "digest"
|
|
|
6
6
|
require_relative "cache_key"
|
|
7
7
|
require_relative "platform"
|
|
8
8
|
require_relative "extractor"
|
|
9
|
+
require_relative "elf_inspector"
|
|
10
|
+
require_relative "glibc"
|
|
9
11
|
require_relative "logger"
|
|
10
12
|
|
|
11
13
|
module Prebake
|
|
@@ -38,6 +40,11 @@ module Prebake
|
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
if verify_checksum(cache_key, expected_checksum, cached_gem)
|
|
43
|
+
unless portable_for_host?(cached_gem)
|
|
44
|
+
# Binary is valid for other hosts; don't delete from backend.
|
|
45
|
+
return super
|
|
46
|
+
end
|
|
47
|
+
|
|
41
48
|
installed = begin
|
|
42
49
|
install_from_cache(cached_gem)
|
|
43
50
|
rescue StandardError => e
|
|
@@ -78,6 +85,18 @@ module Prebake
|
|
|
78
85
|
end
|
|
79
86
|
end
|
|
80
87
|
|
|
88
|
+
def portable_for_host?(gem_path)
|
|
89
|
+
# Cache key already segregates platforms; glibc check only applies on linux.
|
|
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
|
|
98
|
+
end
|
|
99
|
+
|
|
81
100
|
def install_from_cache(gem_path)
|
|
82
101
|
count = Extractor.install(gem_path, @spec)
|
|
83
102
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "logger"
|
|
5
|
+
|
|
6
|
+
module Prebake
|
|
7
|
+
module ExtensionValidator
|
|
8
|
+
BINARY_GLOB = "*.{so,bundle,dll}"
|
|
9
|
+
|
|
10
|
+
def self.validate_all
|
|
11
|
+
Bundler.definition.specs.each { |spec| validate(spec) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.validate(spec)
|
|
15
|
+
ext_dir = spec.extension_dir
|
|
16
|
+
return unless File.exist?(File.join(ext_dir, ".prebake"))
|
|
17
|
+
|
|
18
|
+
# Fast path: root-level binaries already present
|
|
19
|
+
return if Dir.glob(File.join(ext_dir, BINARY_GLOB)).any?
|
|
20
|
+
|
|
21
|
+
# Scan known-broken nested patterns
|
|
22
|
+
nested = Dir.glob(File.join(ext_dir, "extension", "*", "*", BINARY_GLOB))
|
|
23
|
+
nested.concat(Dir.glob(File.join(ext_dir, "lib", BINARY_GLOB)))
|
|
24
|
+
|
|
25
|
+
nested.each do |binary|
|
|
26
|
+
next if File.symlink?(binary)
|
|
27
|
+
next if File.size(binary).zero?
|
|
28
|
+
|
|
29
|
+
dest = File.join(ext_dir, File.basename(binary))
|
|
30
|
+
next if File.exist?(dest)
|
|
31
|
+
|
|
32
|
+
FileUtils.cp(binary, dest)
|
|
33
|
+
Logger.info "Validator: copied #{File.basename(binary)} to #{ext_dir}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/prebake/extractor.rb
CHANGED
|
@@ -46,6 +46,9 @@ module Prebake
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Mark this extension_dir as prebake-managed for post-install validation
|
|
50
|
+
FileUtils.touch(File.join(spec.extension_dir, ".prebake")) if extracted_count > 0
|
|
51
|
+
|
|
49
52
|
Logger.info "Installed precompiled #{File.basename(gem_path)} " \
|
|
50
53
|
"(#{extracted_count} binary files)"
|
|
51
54
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module Prebake
|
|
6
|
+
module Glibc
|
|
7
|
+
GNU_LIBC_PATTERN = /GLIBC\s+(\d+(?:\.\d+)+)|GNU libc[^\n]*\s(\d+(?:\.\d+)+)/
|
|
8
|
+
|
|
9
|
+
def self.compatible?(required)
|
|
10
|
+
return true unless linux?
|
|
11
|
+
return true if required.nil?
|
|
12
|
+
|
|
13
|
+
detected = detected_version
|
|
14
|
+
return false if detected.nil?
|
|
15
|
+
|
|
16
|
+
Gem::Version.new(detected) >= Gem::Version.new(required)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.detected_version
|
|
20
|
+
return @detected_version if defined?(@detected_version)
|
|
21
|
+
|
|
22
|
+
output = run_ldd
|
|
23
|
+
@detected_version = output ? parse_version(output) : nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.parse_version(output)
|
|
27
|
+
match = output.match(GNU_LIBC_PATTERN)
|
|
28
|
+
match && (match[1] || match[2])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.linux?
|
|
32
|
+
RUBY_PLATFORM.include?("linux")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.reset!
|
|
36
|
+
remove_instance_variable(:@detected_version) if defined?(@detected_version)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.run_ldd
|
|
40
|
+
out, status = Open3.capture2e("ldd", "--version")
|
|
41
|
+
return nil unless status.success?
|
|
42
|
+
|
|
43
|
+
out
|
|
44
|
+
rescue Errno::ENOENT
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/prebake/hooks.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "async_publisher"
|
|
4
|
+
require_relative "extension_validator"
|
|
4
5
|
require_relative "logger"
|
|
5
6
|
|
|
6
7
|
module Prebake
|
|
@@ -21,6 +22,8 @@ module Prebake
|
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
Bundler::Plugin::API.hook("after-install-all") do |_deps|
|
|
25
|
+
ExtensionValidator.validate_all
|
|
26
|
+
|
|
24
27
|
next unless Prebake.push_enabled?
|
|
25
28
|
|
|
26
29
|
AsyncPublisher.wait_for_completion
|
data/lib/prebake/logger.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Prebake
|
|
4
4
|
module Logger
|
|
5
|
-
LEVELS = { debug: 0, info: 1, warn: 2 }.freeze
|
|
5
|
+
LEVELS = { debug: 0, info: 1, warn: 2, silent: 3 }.freeze
|
|
6
6
|
|
|
7
7
|
def self.level
|
|
8
|
-
@level ||= LEVELS.fetch(ENV.fetch("PREBAKE_LOG_LEVEL", "
|
|
8
|
+
@level ||= LEVELS.fetch(ENV.fetch("PREBAKE_LOG_LEVEL", "silent").to_sym, 3)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def self.debug(msg)
|
data/lib/prebake.rb
CHANGED
|
@@ -19,6 +19,14 @@ module Prebake
|
|
|
19
19
|
enabled? && ENV.fetch("PREBAKE_PUSH_ENABLED", "false") == "true"
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def self.skip_portability_check?
|
|
23
|
+
ENV.fetch("PREBAKE_SKIP_PORTABILITY_CHECK", "false") == "true"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.max_glibc
|
|
27
|
+
ENV.fetch("PREBAKE_MAX_GLIBC", nil)
|
|
28
|
+
end
|
|
29
|
+
|
|
22
30
|
def self.backend
|
|
23
31
|
return @backend if defined?(@backend_loaded)
|
|
24
32
|
|
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.2.
|
|
4
|
+
version: 0.2.10
|
|
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-
|
|
11
|
+
date: 2026-04-20 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
|
|
@@ -31,8 +31,11 @@ files:
|
|
|
31
31
|
- lib/prebake/backends/http_client.rb
|
|
32
32
|
- lib/prebake/backends/s3.rb
|
|
33
33
|
- lib/prebake/cache_key.rb
|
|
34
|
+
- lib/prebake/elf_inspector.rb
|
|
34
35
|
- lib/prebake/ext_builder_patch.rb
|
|
36
|
+
- lib/prebake/extension_validator.rb
|
|
35
37
|
- lib/prebake/extractor.rb
|
|
38
|
+
- lib/prebake/glibc.rb
|
|
36
39
|
- lib/prebake/hooks.rb
|
|
37
40
|
- lib/prebake/logger.rb
|
|
38
41
|
- lib/prebake/platform.rb
|