prebake 0.3.2 → 0.3.3
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 +2 -1
- data/lib/prebake/elf_inspector.rb +5 -2
- data/lib/prebake/ext_builder_patch.rb +11 -1
- data/lib/prebake/extension_validator.rb +1 -1
- data/lib/prebake/extractor.rb +42 -22
- data/lib/prebake/platform_gem_builder.rb +34 -28
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8631c361d4db2193eacf4995fb4127aff87dac604181479f2a4d8dcce984da7
|
|
4
|
+
data.tar.gz: eec69b8190a68c262659ab98e134174a5ea06f9b1ffa25c204bc3b03de5dcb1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58878c01e36b7553feba5fc0077b2affb94d1188aa53e8e65839fd047d89d8e9089c5eae6a1c7258fccf13852e7cc1ea86131711948e301511f8db64294a712f
|
|
7
|
+
data.tar.gz: 3860c5f16d6765e4fbff18c1049e3402610c9961d07cf8cec61115352be954ddf647e37c9c0a6b7055e62bdb6ea4cc3ce7c041ef6306302a8daac29dc91e5643
|
|
@@ -76,7 +76,8 @@ module Prebake
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
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)
|
|
79
|
+
Logger.warn "Skipping push of #{cache_key}: binary requires libruby.so (dynamic Ruby) " \
|
|
80
|
+
"but this is a static Ruby build; binary would crash on this platform"
|
|
80
81
|
FileUtils.rm_f(gem_path)
|
|
81
82
|
return nil
|
|
82
83
|
end
|
|
@@ -10,7 +10,10 @@ module Prebake
|
|
|
10
10
|
module ElfInspector
|
|
11
11
|
def self.required_glibc_for_gem(gem_path)
|
|
12
12
|
versions = []
|
|
13
|
-
each_gem_binary(gem_path)
|
|
13
|
+
each_gem_binary(gem_path) do |binary|
|
|
14
|
+
v = required_glibc(binary)
|
|
15
|
+
versions << v if v
|
|
16
|
+
end
|
|
14
17
|
versions.empty? ? nil : versions.max_by { |v| Gem::Version.new(v) }
|
|
15
18
|
rescue StandardError => e
|
|
16
19
|
# Malformed gem, missing objdump, or I/O error — treat as unknown, let
|
|
@@ -69,7 +72,7 @@ module Prebake
|
|
|
69
72
|
Dir.mktmpdir("prebake-gem") do |tmpdir|
|
|
70
73
|
Gem::Package.new(gem_path).extract_files(tmpdir)
|
|
71
74
|
Dir.glob(File.join(tmpdir, "**/*.{so,bundle,dll}")).each do |binary|
|
|
72
|
-
next if File.symlink?(binary) || File.
|
|
75
|
+
next if File.symlink?(binary) || File.empty?(binary)
|
|
73
76
|
|
|
74
77
|
yield binary
|
|
75
78
|
end
|
|
@@ -11,6 +11,14 @@ require_relative "logger"
|
|
|
11
11
|
|
|
12
12
|
module Prebake
|
|
13
13
|
module ExtBuilderPatch
|
|
14
|
+
# The metrics cops are off for this method deliberately. It is a cascade of
|
|
15
|
+
# cache checks where every failure path falls through to `super`, the real
|
|
16
|
+
# source build. Extracting any stage would mean returning a sentinel for the
|
|
17
|
+
# caller to turn back into `super`, which reads worse than the cascade and
|
|
18
|
+
# risks silently installing a gem with no compiled binaries if a branch is
|
|
19
|
+
# mistranslated.
|
|
20
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
21
|
+
# rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
14
22
|
def build_extensions
|
|
15
23
|
return super unless @spec.extensions.any?
|
|
16
24
|
return super unless Prebake.enabled?
|
|
@@ -59,7 +67,7 @@ module Prebake
|
|
|
59
67
|
|
|
60
68
|
unless installed
|
|
61
69
|
Prebake.backend.delete(cache_key)
|
|
62
|
-
|
|
70
|
+
super
|
|
63
71
|
end
|
|
64
72
|
else
|
|
65
73
|
super
|
|
@@ -67,6 +75,8 @@ module Prebake
|
|
|
67
75
|
ensure
|
|
68
76
|
FileUtils.rm_f(cached_gem) if cached_gem
|
|
69
77
|
end
|
|
78
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
79
|
+
# rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
70
80
|
|
|
71
81
|
private
|
|
72
82
|
|
data/lib/prebake/extractor.rb
CHANGED
|
@@ -17,35 +17,18 @@ module Prebake
|
|
|
17
17
|
|
|
18
18
|
# Copy only binary files (.so, .bundle, .dll) to extension_dir
|
|
19
19
|
Dir.glob(File.join(tmpdir, "**/*.{so,bundle,dll}")).each do |binary|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# Verify path is within tmpdir (prevent traversal)
|
|
25
|
-
real_binary = File.realpath(binary)
|
|
26
|
-
real_tmpdir = File.realpath(tmpdir)
|
|
27
|
-
next unless real_binary.start_with?("#{real_tmpdir}/")
|
|
28
|
-
|
|
29
|
-
relative = binary.sub("#{tmpdir}/", "")
|
|
30
|
-
|
|
31
|
-
# Normalize paths from cached gems where binaries were packaged
|
|
32
|
-
# from gem_dir build artifacts or dirty extension_dirs.
|
|
33
|
-
# ext/<name>/<name>.so → <name>.so (build artifact)
|
|
34
|
-
# lib/<name>/<name>.so → <name>/<name>.so (gem lib path)
|
|
35
|
-
# extension/<platform>/<ver>/<name>.so → <name>.so (extension_dir artifact)
|
|
36
|
-
relative = relative.sub(%r{\Aext/[^/]+/}, "") if relative.start_with?("ext/")
|
|
37
|
-
relative = relative.sub(%r{\Alib/}, "") if relative.start_with?("lib/")
|
|
38
|
-
relative = relative.sub(%r{\Aextensions?/[^/]+/[^/]+/}, "") if relative.start_with?("extension/", "extensions/")
|
|
39
|
-
|
|
40
|
-
dest = File.join(spec.extension_dir, relative)
|
|
20
|
+
next unless safe_binary?(binary, tmpdir)
|
|
21
|
+
|
|
22
|
+
dest = File.join(spec.extension_dir, normalized_path(binary, tmpdir))
|
|
41
23
|
FileUtils.mkdir_p(File.dirname(dest))
|
|
42
24
|
FileUtils.cp(binary, dest)
|
|
25
|
+
restore_in_gem_dir(binary, tmpdir, spec)
|
|
43
26
|
extracted_count += 1
|
|
44
27
|
end
|
|
45
28
|
end
|
|
46
29
|
|
|
47
30
|
# Mark this extension_dir as prebake-managed for post-install validation
|
|
48
|
-
FileUtils.touch(File.join(spec.extension_dir, ".prebake")) if extracted_count
|
|
31
|
+
FileUtils.touch(File.join(spec.extension_dir, ".prebake")) if extracted_count.positive?
|
|
49
32
|
|
|
50
33
|
Logger.info "Installed precompiled #{File.basename(gem_path)} " \
|
|
51
34
|
"(#{extracted_count} binary files)"
|
|
@@ -55,5 +38,42 @@ module Prebake
|
|
|
55
38
|
Logger.warn "Extraction failed for #{File.basename(gem_path)}: #{e.message}"
|
|
56
39
|
raise
|
|
57
40
|
end
|
|
41
|
+
|
|
42
|
+
# Reject symlinks, empty files, and anything resolving outside tmpdir
|
|
43
|
+
# (path traversal via a crafted gem).
|
|
44
|
+
def self.safe_binary?(binary, tmpdir)
|
|
45
|
+
return false if File.symlink?(binary)
|
|
46
|
+
return false if File.empty?(binary)
|
|
47
|
+
|
|
48
|
+
File.realpath(binary).start_with?("#{File.realpath(tmpdir)}/")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Normalize paths from cached gems where binaries were packaged from
|
|
52
|
+
# gem_dir build artifacts or dirty extension_dirs.
|
|
53
|
+
# ext/<name>/<name>.so → <name>.so (build artifact)
|
|
54
|
+
# lib/<name>/<name>.so → <name>/<name>.so (gem lib path)
|
|
55
|
+
# extension/<platform>/<ver>/<name>.so → <name>.so (extension_dir artifact)
|
|
56
|
+
def self.normalized_path(binary, tmpdir)
|
|
57
|
+
relative = binary.sub("#{tmpdir}/", "")
|
|
58
|
+
relative = relative.sub(%r{\Aext/[^/]+/}, "") if relative.start_with?("ext/")
|
|
59
|
+
relative = relative.sub(%r{\Alib/}, "") if relative.start_with?("lib/")
|
|
60
|
+
return relative unless relative.start_with?("extension/", "extensions/")
|
|
61
|
+
|
|
62
|
+
relative.sub(%r{\Aextensions?/[^/]+/[^/]+/}, "")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# A source build leaves binaries in the gem dir too: ffi-compiler builds in
|
|
66
|
+
# place under ext/, and RubyGems copies extconf output into lib/. FFI gems
|
|
67
|
+
# dlopen those exact paths (llhttp-ffi, sassc). Only ext/ and lib/ are
|
|
68
|
+
# restored, because self-hosted cached gems put extension_dir binaries at
|
|
69
|
+
# the gem root, where no build would leave them.
|
|
70
|
+
def self.restore_in_gem_dir(binary, tmpdir, spec)
|
|
71
|
+
relative = binary.delete_prefix("#{tmpdir}/")
|
|
72
|
+
return unless relative.start_with?("ext/", "lib/")
|
|
73
|
+
|
|
74
|
+
dest = File.join(spec.full_gem_path, relative)
|
|
75
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
76
|
+
FileUtils.cp(binary, dest)
|
|
77
|
+
end
|
|
58
78
|
end
|
|
59
79
|
end
|
|
@@ -54,34 +54,7 @@ module Prebake
|
|
|
54
54
|
Dir.glob(File.join(build_dir, "**/*.{so,bundle,dll}")).each { |f| File.delete(f) }
|
|
55
55
|
|
|
56
56
|
ext_dir = @spec.extension_dir
|
|
57
|
-
if ext_dir && File.directory?(ext_dir)
|
|
58
|
-
# Collect binaries at root and one level deep (e.g., nokogiri/nokogiri.so).
|
|
59
|
-
binaries = Dir.glob(File.join(ext_dir, "*.{so,bundle,dll}")) +
|
|
60
|
-
Dir.glob(File.join(ext_dir, "*/*.{so,bundle,dll}"))
|
|
61
|
-
|
|
62
|
-
# Ruby 4.0+ places compiled extensions in extension/<platform>/<abi>/
|
|
63
|
-
# within extension_dir. Collect these too, normalizing their paths
|
|
64
|
-
# to root level so the cached gem is layout-agnostic.
|
|
65
|
-
Dir.glob(File.join(ext_dir, "extension/*/*/*.{so,bundle,dll}")).each do |binary|
|
|
66
|
-
relative = binary.delete_prefix("#{ext_dir}/")
|
|
67
|
-
normalized = relative.sub(%r{\Aextension/[^/]+/[^/]+/}, "")
|
|
68
|
-
# Skip if a root-level binary with the same name already exists
|
|
69
|
-
next if binaries.any? { |b| b.delete_prefix("#{ext_dir}/") == normalized }
|
|
70
|
-
|
|
71
|
-
binaries << binary
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
binaries.each do |binary|
|
|
75
|
-
next if File.symlink?(binary)
|
|
76
|
-
next if File.size(binary).zero?
|
|
77
|
-
relative = binary.delete_prefix("#{ext_dir}/")
|
|
78
|
-
# Normalize extension/<platform>/<abi>/ paths to root level
|
|
79
|
-
relative = relative.sub(%r{\Aextension/[^/]+/[^/]+/}, "")
|
|
80
|
-
dest = File.join(build_dir, relative)
|
|
81
|
-
FileUtils.mkdir_p(File.dirname(dest))
|
|
82
|
-
FileUtils.cp(binary, dest)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
57
|
+
copy_installed_binaries(ext_dir, build_dir) if ext_dir && File.directory?(ext_dir)
|
|
85
58
|
|
|
86
59
|
prefix = "#{build_dir}/"
|
|
87
60
|
compiled = Dir.glob(File.join(build_dir, "**/*.{so,bundle,dll}"))
|
|
@@ -90,5 +63,38 @@ module Prebake
|
|
|
90
63
|
|
|
91
64
|
platform_spec
|
|
92
65
|
end
|
|
66
|
+
|
|
67
|
+
def copy_installed_binaries(ext_dir, build_dir)
|
|
68
|
+
installed_binaries(ext_dir).each do |binary|
|
|
69
|
+
next if File.symlink?(binary)
|
|
70
|
+
next if File.empty?(binary)
|
|
71
|
+
|
|
72
|
+
dest = File.join(build_dir, root_level_path(binary, ext_dir))
|
|
73
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
74
|
+
FileUtils.cp(binary, dest)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Binaries at root and one level deep (e.g., nokogiri/nokogiri.so), plus the
|
|
79
|
+
# extension/<platform>/<abi>/ layout Ruby 4.0+ installs into. A nested
|
|
80
|
+
# binary is skipped when a root-level one already claims its normalized
|
|
81
|
+
# path, so the cached gem stays layout-agnostic.
|
|
82
|
+
def installed_binaries(ext_dir)
|
|
83
|
+
binaries = Dir.glob(File.join(ext_dir, "*.{so,bundle,dll}")) +
|
|
84
|
+
Dir.glob(File.join(ext_dir, "*/*.{so,bundle,dll}"))
|
|
85
|
+
|
|
86
|
+
Dir.glob(File.join(ext_dir, "extension/*/*/*.{so,bundle,dll}")).each do |binary|
|
|
87
|
+
normalized = root_level_path(binary, ext_dir)
|
|
88
|
+
next if binaries.any? { |b| b.delete_prefix("#{ext_dir}/") == normalized }
|
|
89
|
+
|
|
90
|
+
binaries << binary
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
binaries
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def root_level_path(binary, ext_dir)
|
|
97
|
+
binary.delete_prefix("#{ext_dir}/").sub(%r{\Aextension/[^/]+/[^/]+/}, "")
|
|
98
|
+
end
|
|
93
99
|
end
|
|
94
100
|
end
|
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.3.
|
|
4
|
+
version: 0.3.3
|
|
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-09-
|
|
11
|
+
date: 2026-09-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
|