Class: Kompo::FindNativeExtensions

Inherits:
Taski::Task
  • Object
show all
Defined in:
lib/kompo/tasks/find_native_extensions.rb

Overview

Find native gem extensions that need to be built Exports:

- extensions: Array of hashes with extension info (dir_name, gem_ext_name, is_rust)

Instance Method Summary collapse

Instance Method Details

#runObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/kompo/tasks/find_native_extensions.rb', line 12

def run
  @extensions = []

  ruby_version = InstallRuby.ruby_version
  ruby_build_path = InstallRuby.ruby_build_path
  ruby_install_dir = InstallRuby.ruby_install_dir

  # Get Init functions already defined in libruby-static
  builtin_init_funcs = get_builtin_init_functions(ruby_install_dir)

  # Find bundled gems with native extensions (Ruby 4.0+)
  # Skip if --no-stdlib is specified (bundled gems are part of stdlib)
  no_stdlib = Taski.args.fetch(:no_stdlib, false)
  unless no_stdlib
    bundled_gems_dir = File.join(ruby_build_path, "ruby-#{ruby_version}", ".bundle", "gems")
    find_bundled_gem_extensions(bundled_gems_dir, builtin_init_funcs)
  end

  # Skip user gems if no Gemfile
  unless CopyGemfile.gemfile_exists
    puts "No Gemfile, skipping user gem native extensions"
    puts "Found #{@extensions.size} native extensions to build"
    return
  end

  bundle_ruby_dir = BundleInstall.bundle_ruby_dir

  # Find all native extensions in installed gems
  extconf_files = Dir.glob(File.join(bundle_ruby_dir, "gems/**/extconf.rb"))

  extconf_files.each do |extconf_path|
    dir_name = File.dirname(extconf_path)
    gem_ext_name = File.basename(dir_name)

    # Skip if Init function is already defined in libruby-static
    # This catches gems like prism that are compiled into Ruby core
    init_func = "Init_#{gem_ext_name}"
    if builtin_init_funcs.include?(init_func)
      puts "skip: #{gem_ext_name} is already built into Ruby (#{init_func} found in libruby-static)"
      next
    end

    # Skip if this extension is part of Ruby standard library
    ruby_std_lib = dir_name.split("/").drop_while { |p| p != "ext" }.join("/")
    ruby_ext_objects = Dir.glob(File.join(ruby_build_path, "ruby-#{ruby_version}", "ext", "**", "*.o"))
    if ruby_ext_objects.any? { |o| o.include?(ruby_std_lib) }
      puts "skip: #{gem_ext_name} is included in Ruby standard library"
      next
    end

    # Check if already added (e.g., as bundled gem)
    # Prefer Gemfile versions over prebuilt bundled gems
    existing = @extensions.find { |e| e[:gem_ext_name] == gem_ext_name }
    if existing
      if existing[:is_prebuilt]
        # Replace prebuilt bundled gem with Gemfile version
        puts "replacing: #{gem_ext_name} prebuilt bundled gem with Gemfile version"
        @extensions.delete(existing)
      else
        # Already added as non-prebuilt, skip
        puts "skip: #{gem_ext_name} is already added"
        next
      end
    end

    cargo_toml = File.join(dir_name, "Cargo.toml")
    is_rust = File.exist?(cargo_toml)

    @extensions << {
      dir_name: dir_name,
      gem_ext_name: gem_ext_name,
      is_rust: is_rust,
      cargo_toml: is_rust ? cargo_toml : nil,
      is_prebuilt: false
    }
  end

  puts "Found #{@extensions.size} native extensions to build"
end