kompo 0.3.2 → 0.4.0
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/CHANGELOG.md +20 -0
- data/Gemfile +7 -7
- data/Gemfile.lock +2 -2
- data/README.md +38 -13
- data/Rakefile +6 -6
- data/exe/kompo +21 -19
- data/lib/kompo/cache.rb +7 -7
- data/lib/kompo/kompo_ignore.rb +2 -2
- data/lib/kompo/tasks/build_native_gem.rb +48 -15
- data/lib/kompo/tasks/bundle_install.rb +42 -42
- data/lib/kompo/tasks/cargo_path.rb +13 -13
- data/lib/kompo/tasks/check_stdlibs.rb +6 -6
- data/lib/kompo/tasks/collect_dependencies.rb +23 -15
- data/lib/kompo/tasks/copy_gemfile.rb +19 -11
- data/lib/kompo/tasks/copy_project_files.rb +4 -4
- data/lib/kompo/tasks/find_native_extensions.rb +80 -17
- data/lib/kompo/tasks/homebrew.rb +15 -15
- data/lib/kompo/tasks/install_deps.rb +36 -36
- data/lib/kompo/tasks/kompo_vfs_path.rb +41 -32
- data/lib/kompo/tasks/kompo_vfs_version_check.rb +6 -4
- data/lib/kompo/tasks/make_fs_c.rb +30 -24
- data/lib/kompo/tasks/make_main_c.rb +6 -6
- data/lib/kompo/tasks/packing.rb +63 -48
- data/lib/kompo/tasks/ruby_build_path.rb +60 -15
- data/lib/kompo/version.rb +2 -2
- data/lib/kompo.rb +28 -24
- metadata +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "open3"
|
|
4
4
|
|
|
5
5
|
module Kompo
|
|
6
6
|
# Section to get the Cargo path.
|
|
@@ -16,15 +16,15 @@ module Kompo
|
|
|
16
16
|
class Installed < Taski::Task
|
|
17
17
|
def run
|
|
18
18
|
# First check PATH, then fallback to default rustup location
|
|
19
|
-
cargo_in_path, = Open3.capture2(
|
|
19
|
+
cargo_in_path, = Open3.capture2("which", "cargo", err: File::NULL)
|
|
20
20
|
cargo_in_path = cargo_in_path.chomp
|
|
21
21
|
@path = if cargo_in_path.empty?
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
File.expand_path("~/.cargo/bin/cargo")
|
|
23
|
+
else
|
|
24
|
+
cargo_in_path
|
|
25
|
+
end
|
|
26
26
|
puts "Cargo path: #{@path}"
|
|
27
|
-
version_output, = Open3.capture2e(@path,
|
|
27
|
+
version_output, = Open3.capture2e(@path, "--version")
|
|
28
28
|
puts "Cargo version: #{version_output.chomp}"
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -32,14 +32,14 @@ module Kompo
|
|
|
32
32
|
# Install Cargo via rustup and return the path
|
|
33
33
|
class Install < Taski::Task
|
|
34
34
|
def run
|
|
35
|
-
puts
|
|
35
|
+
puts "Cargo not found. Installing via rustup..."
|
|
36
36
|
system("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y")
|
|
37
37
|
|
|
38
|
-
@path = File.expand_path(
|
|
39
|
-
raise
|
|
38
|
+
@path = File.expand_path("~/.cargo/bin/cargo")
|
|
39
|
+
raise "Failed to install Cargo" unless File.executable?(@path)
|
|
40
40
|
|
|
41
41
|
puts "Cargo installed at: #{@path}"
|
|
42
|
-
version_output, = Open3.capture2e(@path,
|
|
42
|
+
version_output, = Open3.capture2e(@path, "--version")
|
|
43
43
|
puts "Cargo version: #{version_output.chomp}"
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -48,11 +48,11 @@ module Kompo
|
|
|
48
48
|
|
|
49
49
|
def cargo_installed?
|
|
50
50
|
# Check if cargo is in PATH
|
|
51
|
-
cargo_in_path, = Open3.capture2(
|
|
51
|
+
cargo_in_path, = Open3.capture2("which", "cargo", err: File::NULL)
|
|
52
52
|
return true unless cargo_in_path.chomp.empty?
|
|
53
53
|
|
|
54
54
|
# Check default rustup installation location
|
|
55
|
-
home_cargo = File.expand_path(
|
|
55
|
+
home_cargo = File.expand_path("~/.cargo/bin/cargo")
|
|
56
56
|
File.executable?(home_cargo)
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "open3"
|
|
4
4
|
|
|
5
5
|
module Kompo
|
|
6
6
|
# Get Ruby standard library paths from installed Ruby
|
|
@@ -12,7 +12,7 @@ module Kompo
|
|
|
12
12
|
no_stdlib = Taski.args.fetch(:no_stdlib, false)
|
|
13
13
|
if no_stdlib
|
|
14
14
|
@paths = []
|
|
15
|
-
puts
|
|
15
|
+
puts "Skipping standard library (--no-stdlib)"
|
|
16
16
|
return
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -24,9 +24,9 @@ module Kompo
|
|
|
24
24
|
# Include the Ruby standard library root directory
|
|
25
25
|
# This includes bundler and other default gems that are not in $:
|
|
26
26
|
# Ruby uses "X.Y.0" format for lib/ruby paths (e.g., "3.4.0" not "3.4")
|
|
27
|
-
stdlib_root = File.join(ruby_install_dir,
|
|
27
|
+
stdlib_root = File.join(ruby_install_dir, "lib", "ruby", "#{ruby_major_minor}.0")
|
|
28
28
|
# RubyGems needs gemspec files in specifications/ directory
|
|
29
|
-
gems_specs_root = File.join(ruby_install_dir,
|
|
29
|
+
gems_specs_root = File.join(ruby_install_dir, "lib", "ruby", "gems", "#{ruby_major_minor}.0", "specifications")
|
|
30
30
|
|
|
31
31
|
if Dir.exist?(stdlib_root)
|
|
32
32
|
@paths = [stdlib_root, gems_specs_root].select { |p| Dir.exist?(p) }
|
|
@@ -34,7 +34,7 @@ module Kompo
|
|
|
34
34
|
puts "Including gem specifications: #{gems_specs_root}" if Dir.exist?(gems_specs_root)
|
|
35
35
|
else
|
|
36
36
|
# Fallback to $: paths if stdlib root doesn't exist
|
|
37
|
-
output, status = Open3.capture2(ruby,
|
|
37
|
+
output, status = Open3.capture2(ruby, "-e", "puts $:", err: File::NULL)
|
|
38
38
|
unless status.success?
|
|
39
39
|
raise "Failed to get Ruby standard library paths: exit code #{status.exitstatus}, output: #{output}"
|
|
40
40
|
end
|
|
@@ -42,7 +42,7 @@ module Kompo
|
|
|
42
42
|
raw_paths = output.split("\n").reject(&:empty?)
|
|
43
43
|
|
|
44
44
|
@paths = raw_paths.map do |path|
|
|
45
|
-
next nil unless path.start_with?(
|
|
45
|
+
next nil unless path.start_with?("/")
|
|
46
46
|
|
|
47
47
|
if original_ruby_install_dir != ruby_install_dir && path.start_with?(original_ruby_install_dir)
|
|
48
48
|
path.sub(original_ruby_install_dir, ruby_install_dir)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "fileutils"
|
|
4
4
|
|
|
5
5
|
module Kompo
|
|
6
6
|
# Collect all dependencies for packing.
|
|
@@ -20,7 +20,7 @@ module Kompo
|
|
|
20
20
|
project_dir = Taski.args.fetch(:project_dir, Taski.env.working_directory) || Taski.env.working_directory
|
|
21
21
|
output_arg = Taski.args.fetch(:output_dir, Taski.env.working_directory) || Taski.env.working_directory
|
|
22
22
|
|
|
23
|
-
@deps = group(
|
|
23
|
+
@deps = group("Collecting dependencies") do
|
|
24
24
|
collect_dependencies
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -36,33 +36,31 @@ module Kompo
|
|
|
36
36
|
|
|
37
37
|
@ext_paths = get_unique_ext_paths(@work_dir, @deps.ruby_build_path, @deps.ruby_version, @deps.exts_dir)
|
|
38
38
|
@enc_files = [
|
|
39
|
-
File.join(@deps.ruby_build_path, "ruby-#{@deps.ruby_version}",
|
|
40
|
-
File.join(@deps.ruby_build_path, "ruby-#{@deps.ruby_version}",
|
|
41
|
-
File.join(@deps.ruby_build_path, "ruby-#{@deps.ruby_version}",
|
|
39
|
+
File.join(@deps.ruby_build_path, "ruby-#{@deps.ruby_version}", "enc/encinit.o"),
|
|
40
|
+
File.join(@deps.ruby_build_path, "ruby-#{@deps.ruby_version}", "enc/libenc.a"),
|
|
41
|
+
File.join(@deps.ruby_build_path, "ruby-#{@deps.ruby_version}", "enc/libtrans.a")
|
|
42
42
|
]
|
|
43
43
|
|
|
44
|
-
puts
|
|
44
|
+
puts "All dependencies collected for packing"
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
private
|
|
48
48
|
|
|
49
49
|
def collect_dependencies
|
|
50
|
-
# Install dependencies based on platform (Homebrew on macOS, pkg-config check on Linux)
|
|
51
|
-
InstallDeps.run
|
|
52
50
|
deps_lib_paths = InstallDeps.lib_paths
|
|
53
51
|
|
|
54
52
|
ruby_install_dir = InstallRuby.ruby_install_dir
|
|
55
53
|
ruby_version = InstallRuby.ruby_version
|
|
56
54
|
ruby_major_minor = InstallRuby.ruby_major_minor
|
|
57
55
|
ruby_build_path = InstallRuby.ruby_build_path
|
|
58
|
-
ruby_lib = File.join(ruby_install_dir,
|
|
56
|
+
ruby_lib = File.join(ruby_install_dir, "lib")
|
|
59
57
|
|
|
60
58
|
kompo_lib = KompoVfsPath.path
|
|
61
59
|
main_c = MakeMainC.path
|
|
62
60
|
fs_c = MakeFsC.path
|
|
63
61
|
exts_dir = BuildNativeGem.exts_dir
|
|
64
62
|
|
|
65
|
-
puts
|
|
63
|
+
puts "Dependencies collected"
|
|
66
64
|
|
|
67
65
|
Dependencies.new(
|
|
68
66
|
ruby_install_dir: ruby_install_dir,
|
|
@@ -79,19 +77,29 @@ module Kompo
|
|
|
79
77
|
end
|
|
80
78
|
|
|
81
79
|
def get_unique_ext_paths(_work_dir, ruby_build_path, ruby_version, exts_dir)
|
|
80
|
+
ruby_build_dir = File.join(ruby_build_path, "ruby-#{ruby_version}")
|
|
81
|
+
|
|
82
82
|
# Ruby standard extension .o files
|
|
83
|
-
paths = Dir.glob(File.join(
|
|
83
|
+
paths = Dir.glob(File.join(ruby_build_dir, "ext", "**", "*.o"))
|
|
84
|
+
|
|
85
|
+
# Ruby bundled gems .o files (Ruby 4.0+)
|
|
86
|
+
# Skip if --no-stdlib is specified (bundled gems are part of stdlib)
|
|
87
|
+
no_stdlib = Taski.args.fetch(:no_stdlib, false)
|
|
88
|
+
unless no_stdlib
|
|
89
|
+
bundled_gems_paths = Dir.glob(File.join(ruby_build_dir, ".bundle", "gems", "*", "ext", "**", "*.o"))
|
|
90
|
+
paths += bundled_gems_paths
|
|
91
|
+
end
|
|
84
92
|
|
|
85
93
|
# Extract extension path (everything after /ext/) for deduplication
|
|
86
94
|
# e.g., ".../ext/cgi/escape/escape.o" -> "cgi/escape/escape.o"
|
|
87
|
-
ruby_ext_keys = paths.map { |p| p.split(
|
|
95
|
+
ruby_ext_keys = paths.map { |p| p.split("/ext/").last }
|
|
88
96
|
|
|
89
97
|
# Gem extension .o files (excluding duplicates with Ruby std)
|
|
90
98
|
if exts_dir && Dir.exist?(exts_dir)
|
|
91
99
|
gem_ext_paths = Dir.glob("#{exts_dir}/**/*.o")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
.to_h { |p| [p.split("/ext/").last, p] }
|
|
101
|
+
.except(*ruby_ext_keys)
|
|
102
|
+
.values
|
|
95
103
|
paths += gem_ext_paths
|
|
96
104
|
end
|
|
97
105
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "fileutils"
|
|
4
4
|
|
|
5
5
|
module Kompo
|
|
6
6
|
# Copy Gemfile, Gemfile.lock, and gemspec files to working directory if they exist
|
|
@@ -11,25 +11,33 @@ module Kompo
|
|
|
11
11
|
work_dir = WorkDir.path
|
|
12
12
|
project_dir = Taski.args.fetch(:project_dir, Taski.env.working_directory) || Taski.env.working_directory
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# Skip Gemfile processing if --no-gemfile is specified
|
|
15
|
+
if Taski.args[:no_gemfile]
|
|
16
|
+
puts "Skipping Gemfile (--no-gemfile specified)"
|
|
17
|
+
@gemfile_exists = false
|
|
18
|
+
@gemspec_paths = []
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
gemfile_path = File.join(project_dir, "Gemfile")
|
|
23
|
+
gemfile_lock_path = File.join(project_dir, "Gemfile.lock")
|
|
16
24
|
|
|
17
25
|
@gemfile_exists = File.exist?(gemfile_path)
|
|
18
26
|
@gemspec_paths = []
|
|
19
27
|
|
|
20
28
|
if @gemfile_exists
|
|
21
29
|
FileUtils.cp(gemfile_path, work_dir)
|
|
22
|
-
puts
|
|
30
|
+
puts "Copied: Gemfile"
|
|
23
31
|
|
|
24
32
|
if File.exist?(gemfile_lock_path)
|
|
25
33
|
FileUtils.cp(gemfile_lock_path, work_dir)
|
|
26
|
-
puts
|
|
34
|
+
puts "Copied: Gemfile.lock"
|
|
27
35
|
end
|
|
28
36
|
|
|
29
37
|
# Copy gemspec files if Gemfile references gemspec
|
|
30
38
|
copy_gemspec_if_needed(gemfile_path, project_dir, work_dir)
|
|
31
39
|
else
|
|
32
|
-
puts
|
|
40
|
+
puts "No Gemfile found, skipping"
|
|
33
41
|
end
|
|
34
42
|
end
|
|
35
43
|
|
|
@@ -39,8 +47,8 @@ module Kompo
|
|
|
39
47
|
work_dir = WorkDir.path
|
|
40
48
|
return unless work_dir && Dir.exist?(work_dir)
|
|
41
49
|
|
|
42
|
-
gemfile = File.join(work_dir,
|
|
43
|
-
gemfile_lock = File.join(work_dir,
|
|
50
|
+
gemfile = File.join(work_dir, "Gemfile")
|
|
51
|
+
gemfile_lock = File.join(work_dir, "Gemfile.lock")
|
|
44
52
|
|
|
45
53
|
FileUtils.rm_f(gemfile)
|
|
46
54
|
FileUtils.rm_f(gemfile_lock)
|
|
@@ -50,7 +58,7 @@ module Kompo
|
|
|
50
58
|
FileUtils.rm_f(gemspec)
|
|
51
59
|
end
|
|
52
60
|
|
|
53
|
-
puts
|
|
61
|
+
puts "Cleaned up Gemfile"
|
|
54
62
|
end
|
|
55
63
|
|
|
56
64
|
private
|
|
@@ -62,7 +70,7 @@ module Kompo
|
|
|
62
70
|
return unless gemfile_content.match?(/^\s*gemspec\b/)
|
|
63
71
|
|
|
64
72
|
# Copy all .gemspec files from project directory
|
|
65
|
-
gemspec_files = Dir.glob(File.join(project_dir,
|
|
73
|
+
gemspec_files = Dir.glob(File.join(project_dir, "*.gemspec"))
|
|
66
74
|
gemspec_files.each do |gemspec_path|
|
|
67
75
|
dest_path = File.join(work_dir, File.basename(gemspec_path))
|
|
68
76
|
FileUtils.cp(gemspec_path, dest_path)
|
|
@@ -71,7 +79,7 @@ module Kompo
|
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
if gemspec_files.empty?
|
|
74
|
-
warn
|
|
82
|
+
warn "Warning: Gemfile contains gemspec directive but no .gemspec files found"
|
|
75
83
|
end
|
|
76
84
|
end
|
|
77
85
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "fileutils"
|
|
4
4
|
|
|
5
5
|
module Kompo
|
|
6
6
|
# Copy project files (entrypoint and additional files) to working directory
|
|
@@ -10,7 +10,7 @@ module Kompo
|
|
|
10
10
|
def run
|
|
11
11
|
work_dir = WorkDir.path
|
|
12
12
|
project_dir = Taski.args.fetch(:project_dir, Taski.env.working_directory) || Taski.env.working_directory
|
|
13
|
-
entrypoint = Taski.args.fetch(:entrypoint,
|
|
13
|
+
entrypoint = Taski.args.fetch(:entrypoint, "main.rb")
|
|
14
14
|
files = Taski.args.fetch(:files, [])
|
|
15
15
|
|
|
16
16
|
# Copy entrypoint (preserve relative path structure)
|
|
@@ -54,7 +54,7 @@ module Kompo
|
|
|
54
54
|
|
|
55
55
|
if File.directory?(src)
|
|
56
56
|
# Special handling for "." - copy directory contents directly to work_dir
|
|
57
|
-
if file ==
|
|
57
|
+
if file == "." || real_src == real_project_dir
|
|
58
58
|
copy_directory_contents(src, work_dir)
|
|
59
59
|
@additional_paths << work_dir
|
|
60
60
|
else
|
|
@@ -90,7 +90,7 @@ module Kompo
|
|
|
90
90
|
# Only delete if path is inside work_dir
|
|
91
91
|
FileUtils.rm_rf(path) if expanded_path.start_with?(real_work_dir + File::SEPARATOR) && File.exist?(path)
|
|
92
92
|
end
|
|
93
|
-
puts
|
|
93
|
+
puts "Cleaned up project files"
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
private
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "open3"
|
|
4
4
|
|
|
5
5
|
module Kompo
|
|
6
6
|
# Find native gem extensions that need to be built
|
|
@@ -12,22 +12,32 @@ module Kompo
|
|
|
12
12
|
def run
|
|
13
13
|
@extensions = []
|
|
14
14
|
|
|
15
|
-
# Skip if no Gemfile
|
|
16
|
-
unless CopyGemfile.gemfile_exists
|
|
17
|
-
puts 'No Gemfile, no native extensions to find'
|
|
18
|
-
return
|
|
19
|
-
end
|
|
20
|
-
|
|
21
15
|
ruby_version = InstallRuby.ruby_version
|
|
22
16
|
ruby_build_path = InstallRuby.ruby_build_path
|
|
23
17
|
ruby_install_dir = InstallRuby.ruby_install_dir
|
|
24
|
-
bundle_ruby_dir = BundleInstall.bundle_ruby_dir
|
|
25
18
|
|
|
26
19
|
# Get Init functions already defined in libruby-static
|
|
27
20
|
builtin_init_funcs = get_builtin_init_functions(ruby_install_dir)
|
|
28
21
|
|
|
22
|
+
# Find bundled gems with native extensions (Ruby 4.0+)
|
|
23
|
+
# Skip if --no-stdlib is specified (bundled gems are part of stdlib)
|
|
24
|
+
no_stdlib = Taski.args.fetch(:no_stdlib, false)
|
|
25
|
+
unless no_stdlib
|
|
26
|
+
bundled_gems_dir = File.join(ruby_build_path, "ruby-#{ruby_version}", ".bundle", "gems")
|
|
27
|
+
find_bundled_gem_extensions(bundled_gems_dir, builtin_init_funcs)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Skip user gems if no Gemfile
|
|
31
|
+
unless CopyGemfile.gemfile_exists
|
|
32
|
+
puts "No Gemfile, skipping user gem native extensions"
|
|
33
|
+
puts "Found #{@extensions.size} native extensions to build"
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
bundle_ruby_dir = BundleInstall.bundle_ruby_dir
|
|
38
|
+
|
|
29
39
|
# Find all native extensions in installed gems
|
|
30
|
-
extconf_files = Dir.glob(File.join(bundle_ruby_dir,
|
|
40
|
+
extconf_files = Dir.glob(File.join(bundle_ruby_dir, "gems/**/extconf.rb"))
|
|
31
41
|
|
|
32
42
|
extconf_files.each do |extconf_path|
|
|
33
43
|
dir_name = File.dirname(extconf_path)
|
|
@@ -42,21 +52,37 @@ module Kompo
|
|
|
42
52
|
end
|
|
43
53
|
|
|
44
54
|
# Skip if this extension is part of Ruby standard library
|
|
45
|
-
ruby_std_lib = dir_name.split(
|
|
46
|
-
ruby_ext_objects = Dir.glob(File.join(ruby_build_path, "ruby-#{ruby_version}",
|
|
55
|
+
ruby_std_lib = dir_name.split("/").drop_while { |p| p != "ext" }.join("/")
|
|
56
|
+
ruby_ext_objects = Dir.glob(File.join(ruby_build_path, "ruby-#{ruby_version}", "ext", "**", "*.o"))
|
|
47
57
|
if ruby_ext_objects.any? { |o| o.include?(ruby_std_lib) }
|
|
48
58
|
puts "skip: #{gem_ext_name} is included in Ruby standard library"
|
|
49
59
|
next
|
|
50
60
|
end
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
# Check if already added (e.g., as bundled gem)
|
|
63
|
+
# Prefer Gemfile versions over prebuilt bundled gems
|
|
64
|
+
existing = @extensions.find { |e| e[:gem_ext_name] == gem_ext_name }
|
|
65
|
+
if existing
|
|
66
|
+
if existing[:is_prebuilt]
|
|
67
|
+
# Replace prebuilt bundled gem with Gemfile version
|
|
68
|
+
puts "replacing: #{gem_ext_name} prebuilt bundled gem with Gemfile version"
|
|
69
|
+
@extensions.delete(existing)
|
|
70
|
+
else
|
|
71
|
+
# Already added as non-prebuilt, skip
|
|
72
|
+
puts "skip: #{gem_ext_name} is already added"
|
|
73
|
+
next
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
cargo_toml = File.join(dir_name, "Cargo.toml")
|
|
53
78
|
is_rust = File.exist?(cargo_toml)
|
|
54
79
|
|
|
55
80
|
@extensions << {
|
|
56
81
|
dir_name: dir_name,
|
|
57
82
|
gem_ext_name: gem_ext_name,
|
|
58
83
|
is_rust: is_rust,
|
|
59
|
-
cargo_toml: is_rust ? cargo_toml : nil
|
|
84
|
+
cargo_toml: is_rust ? cargo_toml : nil,
|
|
85
|
+
is_prebuilt: false
|
|
60
86
|
}
|
|
61
87
|
end
|
|
62
88
|
|
|
@@ -65,15 +91,52 @@ module Kompo
|
|
|
65
91
|
|
|
66
92
|
private
|
|
67
93
|
|
|
94
|
+
# Find native extensions in Ruby's bundled gems directory (Ruby 4.0+)
|
|
95
|
+
# These are pre-built during Ruby compilation
|
|
96
|
+
def find_bundled_gem_extensions(bundled_gems_dir, builtin_init_funcs)
|
|
97
|
+
return unless Dir.exist?(bundled_gems_dir)
|
|
98
|
+
|
|
99
|
+
extconf_files = Dir.glob(File.join(bundled_gems_dir, "**/extconf.rb"))
|
|
100
|
+
|
|
101
|
+
extconf_files.each do |extconf_path|
|
|
102
|
+
dir_name = File.dirname(extconf_path)
|
|
103
|
+
gem_ext_name = File.basename(dir_name)
|
|
104
|
+
|
|
105
|
+
# Skip if Init function is already defined in libruby-static
|
|
106
|
+
init_func = "Init_#{gem_ext_name}"
|
|
107
|
+
if builtin_init_funcs.include?(init_func)
|
|
108
|
+
puts "skip: #{gem_ext_name} is already built into Ruby (#{init_func} found in libruby-static)"
|
|
109
|
+
next
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Verify that .o files exist (pre-built)
|
|
113
|
+
# Search recursively since object files may be in subdirectories
|
|
114
|
+
o_files = Dir.glob(File.join(dir_name, "**", "*.o"))
|
|
115
|
+
if o_files.empty?
|
|
116
|
+
puts "skip: #{gem_ext_name} has no pre-built .o files"
|
|
117
|
+
next
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
puts "Found bundled gem extension: #{gem_ext_name} (pre-built)"
|
|
121
|
+
@extensions << {
|
|
122
|
+
dir_name: dir_name,
|
|
123
|
+
gem_ext_name: gem_ext_name,
|
|
124
|
+
is_rust: false,
|
|
125
|
+
cargo_toml: nil,
|
|
126
|
+
is_prebuilt: true
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
68
131
|
# Extract Init_ function names from libruby-static using nm
|
|
69
132
|
def get_builtin_init_functions(ruby_install_dir)
|
|
70
|
-
lib_dir = File.join(ruby_install_dir,
|
|
71
|
-
static_lib = Dir.glob(File.join(lib_dir,
|
|
133
|
+
lib_dir = File.join(ruby_install_dir, "lib")
|
|
134
|
+
static_lib = Dir.glob(File.join(lib_dir, "libruby*-static.a")).first
|
|
72
135
|
return Set.new unless static_lib
|
|
73
136
|
|
|
74
137
|
# Use nm to extract defined Init_ symbols (T = text/code section)
|
|
75
138
|
# Use Open3.capture2 with array form to avoid shell injection
|
|
76
|
-
output, status = Open3.capture2(
|
|
139
|
+
output, status = Open3.capture2("nm", static_lib, err: File::NULL)
|
|
77
140
|
return Set.new unless status.success?
|
|
78
141
|
|
|
79
142
|
# Filter lines matching " T _?Init_" pattern and extract the symbol name
|
|
@@ -82,7 +145,7 @@ module Kompo
|
|
|
82
145
|
symbols.map do |line|
|
|
83
146
|
# Third whitespace-separated field is the symbol name
|
|
84
147
|
symbol = line.split[2]
|
|
85
|
-
symbol&.delete_prefix(
|
|
148
|
+
symbol&.delete_prefix("_")
|
|
86
149
|
end.compact.to_set
|
|
87
150
|
end
|
|
88
151
|
end
|
data/lib/kompo/tasks/homebrew.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "open3"
|
|
4
4
|
|
|
5
5
|
module Kompo
|
|
6
6
|
# Section to get the Homebrew path.
|
|
@@ -9,7 +9,7 @@ module Kompo
|
|
|
9
9
|
interfaces :path
|
|
10
10
|
|
|
11
11
|
# Common Homebrew installation paths
|
|
12
|
-
COMMON_BREW_PATHS = [
|
|
12
|
+
COMMON_BREW_PATHS = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"].freeze
|
|
13
13
|
|
|
14
14
|
def impl
|
|
15
15
|
homebrew_installed? ? Installed : Install
|
|
@@ -19,27 +19,27 @@ module Kompo
|
|
|
19
19
|
class Installed < Taski::Task
|
|
20
20
|
def run
|
|
21
21
|
# First check PATH
|
|
22
|
-
brew_in_path, = Open3.capture2(
|
|
22
|
+
brew_in_path, = Open3.capture2("which", "brew", err: File::NULL)
|
|
23
23
|
brew_in_path = brew_in_path.chomp
|
|
24
24
|
@path = if brew_in_path.empty?
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
# Fallback to common installation paths
|
|
26
|
+
HomebrewPath::COMMON_BREW_PATHS.find { |p| File.executable?(p) }
|
|
27
|
+
else
|
|
28
|
+
brew_in_path
|
|
29
|
+
end
|
|
30
30
|
puts "Homebrew path: #{@path}"
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Install Homebrew
|
|
35
35
|
class Install < Taski::Task
|
|
36
|
-
MARKER_FILE = File.expand_path(
|
|
36
|
+
MARKER_FILE = File.expand_path("~/.kompo_installed_homebrew")
|
|
37
37
|
|
|
38
38
|
def run
|
|
39
|
-
puts
|
|
39
|
+
puts "Homebrew not found. Installing..."
|
|
40
40
|
system('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"')
|
|
41
41
|
|
|
42
|
-
brew_in_path, = Open3.capture2(
|
|
42
|
+
brew_in_path, = Open3.capture2("which", "brew", err: File::NULL)
|
|
43
43
|
@path = brew_in_path.chomp
|
|
44
44
|
if @path.empty?
|
|
45
45
|
# Check common installation paths
|
|
@@ -51,7 +51,7 @@ module Kompo
|
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
raise
|
|
54
|
+
raise "Failed to install Homebrew" if @path.nil? || @path.empty?
|
|
55
55
|
|
|
56
56
|
# Mark that kompo installed Homebrew
|
|
57
57
|
File.write(MARKER_FILE, @path)
|
|
@@ -62,10 +62,10 @@ module Kompo
|
|
|
62
62
|
# Only uninstall if kompo installed it
|
|
63
63
|
return unless File.exist?(MARKER_FILE)
|
|
64
64
|
|
|
65
|
-
puts
|
|
65
|
+
puts "Uninstalling Homebrew (installed by kompo)..."
|
|
66
66
|
system('NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)"')
|
|
67
67
|
File.delete(MARKER_FILE) if File.exist?(MARKER_FILE)
|
|
68
|
-
puts
|
|
68
|
+
puts "Homebrew uninstalled"
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -73,7 +73,7 @@ module Kompo
|
|
|
73
73
|
|
|
74
74
|
def homebrew_installed?
|
|
75
75
|
# Check if brew is in PATH
|
|
76
|
-
brew_in_path, = Open3.capture2(
|
|
76
|
+
brew_in_path, = Open3.capture2("which", "brew", err: File::NULL)
|
|
77
77
|
return true unless brew_in_path.chomp.empty?
|
|
78
78
|
|
|
79
79
|
# Check common installation paths
|