Class: Rubycc::Doctor::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/rubycc/doctor/builder.rb

Overview

The on-the-fly build a gem gets when it is not in the verified database: the same path RubyGems takes for a gem install, driven by hand so each stage's outcome can be reported separately.

Per gem: fetch the .gem, read its real extensions list; with none it needs no C toolchain (no_ext). For each extension, run its extconf.rb with the mkmf_shim loaded (so mkmf's conftests and the Makefile it writes route to rubycc), then build the generated Makefile with rmake (which always substitutes rubycc for the compiler/linker). On success the freshly built .so is required in a child process to prove its Init function dlopens.

A failure is pinned to a stage — extconf, compile, link, require or the per-gem timeout — with a short summary of the tool's stderr.

Defined Under Namespace

Classes: Result

Constant Summary collapse

REPO_LIB =

Repository lib/ (this file lives at lib/rubycc/doctor/) and the executables the build shells out to.

File.expand_path("../..", __dir__)
REPO_ROOT =
File.expand_path("../../..", __dir__)
RMAKE_EXE =
File.join(REPO_ROOT, "exe", "rmake")
MKMF_SHIM_REQUIRE =
"-rrubycc/mkmf_shim"

Instance Method Summary collapse

Constructor Details

#initialize(cache_dir:, timeout: 300, out: nil) ⇒ Builder

timeout is the per-gem ceiling in seconds. out receives progress lines.



44
45
46
47
48
# File 'lib/rubycc/doctor/builder.rb', line 44

def initialize(cache_dir:, timeout: 300, out: nil)
  @cache_dir = cache_dir
  @timeout = timeout
  @out = out
end

Instance Method Details

#build(entry) ⇒ Object

Build entry (a Gemfile::Entry). Returns a Result. Never raises: every failure mode is folded into a Result the CLI can print.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/rubycc/doctor/builder.rb', line 52

def build(entry)
  fetcher = Fetcher.new(@cache_dir)
  version = entry.version || fetcher.latest_version(entry.name)
  return Result.new(status: :unknown, reason: "could not resolve a version") unless version

  gem_path =
    begin
      fetcher.download(entry.name, version)
    rescue Fetcher::FetchError => e
      return Result.new(status: :unknown, reason: "fetch failed (offline?): #{e.message}")
    end

  spec = fetcher.spec(gem_path)
  return Result.new(status: :no_ext) if spec.extensions.empty?

  Timeout.timeout(@timeout) { build_extensions(entry.name, version, gem_path, spec) }
rescue Timeout::Error
  Result.new(status: :failed, stage: :timeout, reason: "exceeded #{@timeout}s")
rescue StandardError => e
  Result.new(status: :failed, stage: :internal, reason: "#{e.class}: #{e.message}")
end