Module: Leakferret::Binary

Defined in:
lib/leakferret/binary.rb

Overview

Resolves (and, if needed, downloads) the native ‘leakferret` binary.

The binary is fetched into an absolute, user-writable cache directory rather than into the gem’s own tree. RubyGems builds extensions in a throwaway temp dir, so anything written relative to the gem during ‘gem install` is discarded — the cache path sidesteps that entirely and also lets a plain `gem install` (no extension) work.

Constant Summary collapse

BUNDLED_DIR =

A binary vendored inside the gem, if one was shipped (normally empty).

Pathname.new(__dir__).join('bin').freeze

Class Method Summary collapse

Class Method Details

.cache_dirObject

User-writable cache directory, namespaced by the binary version so a gem upgrade fetches a fresh binary instead of reusing a stale one.



52
53
54
55
56
57
58
59
60
# File 'lib/leakferret/binary.rb', line 52

def cache_dir
  base =
    if Platform.windows?
      ENV['LOCALAPPDATA'] || File.join(Dir.home, 'AppData', 'Local')
    else
      ENV['XDG_CACHE_HOME'] || File.join(Dir.home, '.cache')
    end
  Pathname.new(base).join('leakferret', BINARY_VERSION)
end

.cache_pathObject



62
63
64
# File 'lib/leakferret/binary.rb', line 62

def cache_path
  cache_dir.join(Platform.binary_name)
end

.download_urlObject



66
67
68
69
# File 'lib/leakferret/binary.rb', line 66

def download_url
  'https://github.com/leakferrethq/leakferret/releases/download/' \
    "v#{BINARY_VERSION}/leakferret-#{BINARY_VERSION}-#{Platform.triple}.tar.gz"
end

.ensure!Object

Download and unpack the binary into the cache. Idempotent: a no-op when the binary is already cached. Returns the path; raises on failure.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/leakferret/binary.rb', line 73

def ensure!
  dest = cache_path
  return dest.to_s if dest.file?

  require 'fileutils'
  require 'open-uri'
  require 'zlib'
  require 'rubygems/package'

  FileUtils.mkdir_p(dest.dirname)
  # Stream download -> gunzip -> untar in pure Ruby (no external `tar`,
  # which on Windows mis-reads `C:\` as a remote host). The archive nests
  # everything under leakferret-<version>-<triple>/, so match by basename.
  found = false
  URI.open(download_url) do |io| # rubocop:disable Security/Open
    Zlib::GzipReader.wrap(io) do |gz|
      Gem::Package::TarReader.new(gz) do |tar|
        tar.each do |entry|
          next unless entry.file?
          next unless File.basename(entry.full_name) == Platform.binary_name

          File.binwrite(dest, entry.read)
          found = true
        end
      end
    end
  end
  raise BinaryNotFoundError, "binary not found inside #{download_url}" unless found

  FileUtils.chmod(0o755, dest) unless Platform.windows?
  dest.to_s
end

.install_instructions(candidate) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/leakferret/binary.rb', line 106

def install_instructions(candidate)
  <<~MSG
    leakferret native binary not found, and the automatic download failed.

    Expected it at:
      #{candidate}

    Download the binary for your platform from:
      https://github.com/leakferrethq/leakferret/releases

    then either place it at the path above or point LEAKFERRET_BIN at it.
  MSG
end

.pathObject

Absolute path to the native binary, downloading it on first use if necessary. Resolution order:

1. LEAKFERRET_BIN          — explicit override
2. lib/leakferret/bin/     — a binary vendored in the gem
3. the per-version cache   — fetched on a prior run or at install
4. download into the cache now


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/leakferret/binary.rb', line 29

def path
  override = ENV['LEAKFERRET_BIN']
  unless override.nil? || override.empty?
    unless File.file?(override)
      raise BinaryNotFoundError, "LEAKFERRET_BIN points to a missing file: #{override}"
    end

    return override
  end

  bundled = BUNDLED_DIR.join(Platform.binary_name)
  return bundled.to_s if bundled.file?

  return cache_path.to_s if cache_path.file?

  ensure!
  raise BinaryNotFoundError, install_instructions(cache_path) unless cache_path.file?

  cache_path.to_s
end