Class: Tebako::RuntimeManager

Inherits:
Object
  • Object
show all
Defined in:
lib/tebako/runtime_manager.rb

Overview

Resolution, download, verification and machine-wide caching of prebuilt tebako runtime packages published by tamatebako/tebako-runtime-ruby.

Cache layout (rooted at $TEBAKO_HOME or ~/.tebako):

runtimes/ruby-<ruby-version>-<tebakoabi>-<platform>/
tebako-runtime-<tebakoabi>-<ruby-version>-<platform>[.exe]
sha256    -- digest the installed file was verified against
origin    -- URL the package was downloaded from

Installs are serialized per entry with a flock'd lockfile; packages are downloaded to tmp/, SHA256-verified against the release index and moved into place with an atomic rename, so partial downloads never poison the cache.

Direct Known Subclasses

BootstrapManager

Defined Under Namespace

Classes: DownloadFailed, IndexUnavailable

Constant Summary collapse

DEFAULT_MIRROR =

rubocop:disable Metrics/ClassLength

"https://github.com/tamatebako/tebako-runtime-ruby/releases/download"
RUNTIMES_DIR =
"runtimes"
TMP_DIR =
"tmp"
LOCK_FILE =
".install.lock"
SHA256_FILE =
"sha256"
ORIGIN_FILE =
"origin"
RUNTIME_TYPE =
"ruby"
LOCK_TIMEOUT =
300
DOWNLOAD_ATTEMPTS =
3
RETRY_DELAY =
1.0
REDIRECT_LIMIT =
5
INDEX_FILES =
["manifest.json", "SHA256SUMS.txt"].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cache_root: nil, mirror: nil, lock_timeout: LOCK_TIMEOUT, retry_delay: RETRY_DELAY) ⇒ RuntimeManager

Returns a new instance of RuntimeManager.



93
94
95
96
97
98
# File 'lib/tebako/runtime_manager.rb', line 93

def initialize(cache_root: nil, mirror: nil, lock_timeout: LOCK_TIMEOUT, retry_delay: RETRY_DELAY)
  @cache_root = cache_root || self.class.default_cache_root
  @mirror = (mirror || ENV.fetch(mirror_env_var, nil) || default_mirror).sub(%r{/+\z}, "")
  @lock_timeout = lock_timeout
  @retry_delay = retry_delay
end

Instance Attribute Details

#cache_rootObject (readonly)

Returns the value of attribute cache_root.



100
101
102
# File 'lib/tebako/runtime_manager.rb', line 100

def cache_root
  @cache_root
end

Class Method Details

.default_cache_rootObject



73
74
75
76
77
78
79
80
81
82
# File 'lib/tebako/runtime_manager.rb', line 73

def default_cache_root
  tebako_home = ENV.fetch("TEBAKO_HOME", nil)
  return tebako_home unless tebako_home.nil? || tebako_home.empty?

  if Gem.win_platform?
    File.join(ENV.fetch("LOCALAPPDATA", File.join(Dir.home, "AppData", "Local")), "tebako")
  else
    File.join(Dir.home, ".tebako")
  end
end

.layout(runtime_path) ⇒ Object



88
89
90
# File 'lib/tebako/runtime_manager.rb', line 88

def layout(runtime_path)
  new.layout(runtime_path)
end

.resolve(ruby_version, platform, tebako_version: Tebako::VERSION) ⇒ Object



84
85
86
# File 'lib/tebako/runtime_manager.rb', line 84

def resolve(ruby_version, platform, tebako_version: Tebako::VERSION)
  new.resolve(ruby_version, platform, tebako_version: tebako_version)
end

Instance Method Details

#entriesObject



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/tebako/runtime_manager.rb', line 129

def entries
  base = File.join(@cache_root, cache_subdir)
  return [] unless Dir.exist?(base)

  Dir.children(base).sort.filter_map do |name|
    path = File.join(base, name)
    next unless File.directory?(path)

    { name: name, path: path, size_bytes: dir_size(path), installed_at: File.mtime(path) }
  end
end

#layout(runtime_path) ⇒ Object

Extract the runtime package's filesystem layout next to the cached package (idempotent) and return the layout root. The layout is the authoritative source of the runtime's arch conventions (rbconfig location, gem extension dir naming), which the application image must match: ruby's compiled-in search paths come from the runtime build, while a local packaging environment names them after the press machine (macOS embeds the kernel version, e.g. arm64-darwin23 vs -darwin24).



120
121
122
123
124
125
126
127
# File 'lib/tebako/runtime_manager.rb', line 120

def layout(runtime_path)
  layout_dir = File.join(File.dirname(runtime_path), "layout")
  return layout_dir if File.directory?(File.join(layout_dir, "lib"))

  FileUtils.mkdir_p(layout_dir)
  Tebako::BuildHelpers.run_with_capture_v([runtime_path, "--tebako-extract", layout_dir])
  layout_dir
end

#prune(all: false, older_than_days: nil) ⇒ Object

Raises:

  • (ArgumentError)


141
142
143
144
145
146
147
148
149
150
151
# File 'lib/tebako/runtime_manager.rb', line 141

def prune(all: false, older_than_days: nil)
  raise ArgumentError, "prune requires :all or :older_than_days" unless all || older_than_days

  cutoff = older_than_days && (Time.now - (older_than_days * 86_400))
  entries.each_with_object([]) do |entry, removed|
    next unless all || entry[:installed_at] < cutoff

    FileUtils.rm_rf(entry[:path], secure: true)
    removed << entry[:name]
  end
end

#resolve(ruby_version, platform, tebako_version: Tebako::VERSION) ⇒ Object



102
103
104
105
106
107
108
109
110
111
# File 'lib/tebako/runtime_manager.rb', line 102

def resolve(ruby_version, platform, tebako_version: Tebako::VERSION)
  dir = entry_dir(ruby_version, platform, tebako_version)
  executable = File.join(dir, runtime_filename(ruby_version, platform, tebako_version))
  return executable if File.file?(executable)

  with_entry_lock(dir, runtime_ref(ruby_version, platform, tebako_version)) do
    install(executable, ruby_version, platform, tebako_version) unless File.file?(executable)
  end
  executable
end