Module: Plushie::Binary

Defined in:
lib/plushie/binary.rb

Overview

Resolves the path to the plushie renderer binary.

Resolution order (explicit config raises if file missing; implicit discovery silently tries the next option):

  1. PLUSHIE_BINARY_PATH environment variable (explicit)
  2. Plushie.configuration.binary_path (explicit)
  3. Custom extension build in _build/plushie/custom/target/ (implicit)
  4. Downloaded binary in _build/plushie/bin/ (implicit)
  5. Sibling plushie-rust checkout's target/release,debug/ (implicit)
  6. System PATH (implicit)

Class Method Summary collapse

Class Method Details

.arch_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Map Ruby architecture to binary architecture name.



154
155
156
157
158
159
160
# File 'lib/plushie/binary.rb', line 154

def arch_name
  case RbConfig::CONFIG["host_cpu"]
  when /x86_64|amd64/i then "x86_64"
  when /aarch64|arm64/i then "aarch64"
  else raise Error, "unsupported architecture: #{RbConfig::CONFIG["host_cpu"]}"
  end
end

.binary_nameString

Returns platform-specific binary filename.

Returns:

  • (String)

    platform-specific binary filename



212
213
214
215
216
# File 'lib/plushie/binary.rb', line 212

def binary_name
  name = "plushie-renderer-#{os_name}-#{arch_name}"
  name += ".exe" if Gem.win_platform?
  name
end

.custom_build_pathString?

Path to a custom extension build binary. Checks both release and debug profiles.

Returns:

  • (String, nil)


117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/plushie/binary.rb', line 117

def custom_build_path
  build_dir = File.join("_build", "plushie", "custom", "target")
  return nil unless File.directory?(build_dir)

  bin_name = Plushie.configuration.build_name
  ext = Gem.win_platform? ? ".exe" : ""

  %w[release debug].each do |profile|
    path = File.join(build_dir, profile, "#{bin_name}#{ext}")
    return path if File.exist?(path)
  end
  nil
end

.download!(version: PLUSHIE_RUST_VERSION, dest: nil) ⇒ String

Download the precompiled binary for the current platform. Verifies the SHA-256 checksum against a .sha256 sidecar file.

Parameters:

  • version (String) (defaults to: PLUSHIE_RUST_VERSION)

    plushie-rust version (default: PLUSHIE_RUST_VERSION)

  • dest (String, nil) (defaults to: nil)

    override destination path (default: _build/plushie/bin/name)

Returns:

  • (String)

    path to the downloaded binary



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/plushie/binary.rb', line 168

def download!(version: PLUSHIE_RUST_VERSION, dest: nil)
  require "net/http"
  require "uri"
  require "fileutils"
  require "digest"

  url = release_url(version)
  checksum_url = "#{url}.sha256"
  if dest
    FileUtils.mkdir_p(File.dirname(dest))
  else
    dir = File.join("_build", "plushie", "bin")
    FileUtils.mkdir_p(dir)
    dest = File.join(dir, binary_name)
  end

  warn "Downloading plushie #{version} for #{os_name}-#{arch_name}..."

  binary_data = fetch_url(url)
  checksum_data = fetch_url(checksum_url)

  # The .sha256 file contains "hexdigest  filename\n" or just "hexdigest\n"
  expected_sha = checksum_data.strip.split(/\s+/).first

  actual_sha = Digest::SHA256.hexdigest(binary_data)

  unless actual_sha == expected_sha
    raise Error, "checksum mismatch for #{binary_name}: " \
      "expected #{expected_sha}, got #{actual_sha}"
  end

  File.binwrite(dest, binary_data)
  File.chmod(0o755, dest) unless Gem.win_platform?
  warn "Saved to #{dest} (#{binary_data.bytesize} bytes, SHA-256 verified)"

  dest
end

.download_wasm!(version: PLUSHIE_RUST_VERSION, force: false, dir: nil) ⇒ String

Download the WASM renderer tarball and extract it.

Parameters:

  • version (String) (defaults to: PLUSHIE_RUST_VERSION)

    plushie-rust version (default: PLUSHIE_RUST_VERSION)

  • force (Boolean) (defaults to: false)

    re-download even if files exist

  • dir (String, nil) (defaults to: nil)

    override output directory (default: wasm_path)

Returns:

  • (String)

    path to the WASM directory



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/plushie/binary.rb', line 257

def download_wasm!(version: PLUSHIE_RUST_VERSION, force: false, dir: nil)
  require "net/http"
  require "uri"
  require "fileutils"
  require "digest"
  require "rubygems/package"
  require "zlib"
  require "stringio"

  dir ||= wasm_path
  js_path = File.join(dir, "plushie_renderer_wasm.js")
  wasm_file = File.join(dir, "plushie_renderer_wasm_bg.wasm")

  if !force && File.exist?(js_path) && File.exist?(wasm_file)
    warn "WASM files already exist in #{dir}. Use force: true to re-download."
    return dir
  end

  archive_name = "plushie-renderer-wasm.tar.gz"
  url = "https://github.com/plushie-ui/plushie-renderer/releases/download/v#{version}/#{archive_name}"
  checksum_url = "#{url}.sha256"

  warn "Downloading #{archive_name}..."

  archive_data = fetch_url(url)
  checksum_data = fetch_url(checksum_url)

  expected_sha = checksum_data.strip.split(/\s+/).first
  actual_sha = Digest::SHA256.hexdigest(archive_data)

  unless actual_sha == expected_sha
    raise Error, "checksum mismatch for #{archive_name}: " \
      "expected #{expected_sha}, got #{actual_sha}"
  end

  FileUtils.mkdir_p(dir)

  # Extract tar.gz using Ruby built-ins
  io = StringIO.new(archive_data)
  Zlib::GzipReader.wrap(io) do |gz|
    Gem::Package::TarReader.new(gz) do |tar|
      tar.each do |entry|
        next unless entry.file?
        dest = File.join(dir, File.basename(entry.full_name))
        File.binwrite(dest, entry.read)
      end
    end
  end

  warn "Installed WASM files to #{dir} (SHA-256 verified)"
  dir
end

.downloaded_pathString?

Path to the downloaded precompiled binary.

Returns:

  • (String, nil)


134
135
136
137
138
139
# File 'lib/plushie/binary.rb', line 134

def downloaded_path
  dir = File.join("_build", "plushie", "bin")
  name = binary_name
  path = File.join(dir, name)
  File.exist?(path) ? path : nil
end

.os_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Map Ruby platform to binary OS name.



143
144
145
146
147
148
149
150
# File 'lib/plushie/binary.rb', line 143

def os_name
  case RbConfig::CONFIG["host_os"]
  when /linux/i then "linux"
  when /darwin/i then "darwin"
  when /mswin|mingw|cygwin/i then "windows"
  else raise Error, "unsupported OS: #{RbConfig::CONFIG["host_os"]}"
  end
end

.pathString?

Resolve the binary path, or return nil.

Returns:

  • (String, nil)


48
49
50
# File 'lib/plushie/binary.rb', line 48

def path
  resolve
end

.path!String

Resolve and return the binary path, or raise with helpful instructions.

Returns:

  • (String)

    path to the plushie binary

Raises:



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/plushie/binary.rb', line 25

def path!
  path = resolve
  unless path
    raise Error, <<~MSG.chomp
      plushie binary not found.

      To download a precompiled binary:
        rake plushie:download

      To build from source:
        rake plushie:build

      To use an existing binary:
        export PLUSHIE_BINARY_PATH=/path/to/plushie
    MSG
  end
  raise Error, "plushie binary not executable: #{path}" unless File.executable?(path)
  path
end

.release_url(version) ⇒ String

Returns GitHub release download URL.

Returns:

  • (String)

    GitHub release download URL



207
208
209
# File 'lib/plushie/binary.rb', line 207

def release_url(version)
  "https://github.com/plushie-ui/plushie-renderer/releases/download/v#{version}/#{binary_name}"
end

.resolveString?

Full resolution chain.

Returns:

  • (String, nil)


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
# File 'lib/plushie/binary.rb', line 55

def resolve
  # 1. Explicit env var (raises if set but missing)
  if (env_path = ENV["PLUSHIE_BINARY_PATH"])
    return env_path if File.exist?(env_path)
    raise Error, "PLUSHIE_BINARY_PATH set but file not found: #{env_path}"
  end

  # 2. Explicit config (raises if set but missing)
  if (config_path = Plushie.configuration.binary_path)
    return config_path if File.exist?(config_path)
    raise Error, "Plushie.configuration.binary_path set to #{config_path.inspect} but file not found"
  end

  # 3. Custom extension build
  custom = custom_build_path
  return custom if custom

  # 4. Downloaded binary
  downloaded = downloaded_path
  return downloaded if downloaded

  # 5. Sibling plushie-rust checkout (convenient during local SDK development)
  sibling = sibling_target_path
  return sibling if sibling

  # 6. System PATH
  which("plushie")
end

.sibling_target_pathString?

Path to a plushie-renderer binary built in a sibling plushie-rust checkout. This is purely a developer-convenience fallback: it lets the Ruby SDK find the renderer when both repos live next to each other without requiring PLUSHIE_BINARY_PATH on every invocation. Checks release first, then debug.

The lookup roots are, in order:

  • $PLUSHIE_RUST_SOURCE_PATH (if set)
  • ../plushie-rust (relative to Dir.pwd)

Returns:

  • (String, nil)


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/plushie/binary.rb', line 95

def sibling_target_path
  roots = []
  if (env_root = ENV["PLUSHIE_RUST_SOURCE_PATH"]) && !env_root.empty?
    roots << env_root
  end
  roots << File.expand_path("../plushie-rust", Dir.pwd)

  ext = Gem.win_platform? ? ".exe" : ""
  roots.each do |root|
    next unless File.directory?(root)
    %w[release debug].each do |profile|
      path = File.join(root, "target", profile, "plushie-renderer#{ext}")
      return path if File.executable?(path)
    end
  end
  nil
end

.wasm_pathString

Returns path to the WASM output directory.

Returns:

  • (String)

    path to the WASM output directory



311
312
313
# File 'lib/plushie/binary.rb', line 311

def wasm_path
  File.join("_build", "plushie-renderer", "wasm")
end

.which(cmd) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Search PATH for an executable.



220
221
222
223
224
225
226
# File 'lib/plushie/binary.rb', line 220

def which(cmd)
  ENV["PATH"]&.split(File::PATH_SEPARATOR)&.each do |dir|
    path = File.join(dir, cmd)
    return path if File.executable?(path)
  end
  nil
end