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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5526a96f538034fd54828e704413a28c12d7ee26e6b7489bc93a8366615bab9d
4
- data.tar.gz: 6f5c1ad1a6ae983ca8a0d7ddfbe13f633da314eefa5609716cb1c4a4569a804e
3
+ metadata.gz: 5016083674e522c23d95fa0526df6b35f159e529b0d3116fc24daf70e1c52df9
4
+ data.tar.gz: 7a32973b781cb342998d150c5fe75b944c4865055df2f168c2bebeb48fa1450c
5
5
  SHA512:
6
- metadata.gz: 616f1f3d581285820312d232bb64465cd179161f8c4b846db4d9c8904acfb08b92137651ceecb932eb83bee811ebdd6cf553dc764168bb2597b6916e8435c896
7
- data.tar.gz: 4583a32a07fd38a498e3976a335fc106cae77e951c3a858d0a8d2df97af0c79fe2a08d397f7310779c551221412889d8903fa59f91fe566b629c2b5f40a87b95
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
@@ -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
@@ -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", "warn").to_sym, 1)
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.8
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-03-30 00:00:00.000000000 Z
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