Module: McptaskRailsRunner::Binary
- Defined in:
- lib/mcptask_rails_runner/binary.rb
Overview
Locates, installs and executes the bundled Go binary.
Constant Summary collapse
- GEM_ROOT =
File.("../..", __dir__)
- SUPPORTED_PLATFORMS =
%w[ arm64-darwin x86_64-darwin aarch64-linux x86_64-linux x64-mingw-ucrt ].freeze
- INSTALL_DIR =
Where the binary is installed to live, outside every gem directory.
The scheduled job runs this path, and it has to be one that survives a
bundle update: a job pointing into gems/mcptask-rails-runner-- /libexec dies the moment the gem is bumped, at 08:00, in a log nobody reads. Under the runner's own ~/.mcptask rather than /usr/local/bin, because bundle installhas no sudo and a gem cannot write there. The Go side knows the same path — see InstalledBinary in internal/install/schedule.go. File.join(Dir.home, ".mcptask", "bin")
- VERSION_LINE =
The line
mcptask_runner versionprints the version on. /Version:\s*(\S+)/
Class Method Summary collapse
-
.bundled_path(gem_root: GEM_ROOT) ⇒ Object
The copy that ships inside this gem, which is the one the Gemfile pins.
-
.copy(source, target) ⇒ Object
Written beside the target and renamed into place: a half-copied binary over a working one is a scheduled job that fails on a corrupt file, and the rename also sidesteps the ETXTBSY a running copy would give.
-
.exec!(*args) ⇒ Object
Replaces the current process, so the binary's exit code and signal handling reach the caller (rake) unchanged.
-
.exec_installed!(*args) ⇒ Object
Installs the binary outside the gem first, then runs that copy.
- .executable_name ⇒ Object
-
.install!(install_dir: INSTALL_DIR, gem_root: GEM_ROOT, out: $stdout) ⇒ Object
Copies the bundled binary out of the gem, and returns what to run.
-
.installed_path(install_dir: INSTALL_DIR) ⇒ Object
The version-free path the installed copy lives at.
-
.installed_version(target) ⇒ Object
What the installed copy reports about itself, or nil when there is none — or when it cannot be asked, which a stray file at that path cannot be.
-
.path(gem_root: GEM_ROOT) ⇒ Object
Absolute path to the binary this gem runs.
Class Method Details
.bundled_path(gem_root: GEM_ROOT) ⇒ Object
The copy that ships inside this gem, which is the one the Gemfile pins.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/mcptask_rails_runner/binary.rb', line 49 def bundled_path(gem_root: GEM_ROOT) exe = File.join(gem_root, "libexec", executable_name) return exe if File.file?(exe) raise Error, <<~MSG mcptask_runner binary not found at #{exe}. The platform gem for #{Gem::Platform.local} was probably not installed. Supported platforms: #{SUPPORTED_PLATFORMS.join(', ')}. On a supported platform, re-run `bundle install` (and make sure the platform is in Gemfile.lock: `bundle lock --add-platform #{Gem::Platform.local}`). On an unsupported platform, install the binary from https://github.com/jchsoft/mcptask-releases and point the MCPTASK_RUNNER_BIN environment variable at it. MSG end |
.copy(source, target) ⇒ Object
Written beside the target and renamed into place: a half-copied binary over a working one is a scheduled job that fails on a corrupt file, and the rename also sidesteps the ETXTBSY a running copy would give.
146 147 148 149 150 151 152 |
# File 'lib/mcptask_rails_runner/binary.rb', line 146 def copy(source, target) FileUtils.mkdir_p(File.dirname(target)) staged = "#{target}.new" FileUtils.cp(source, staged) FileUtils.chmod(0o755, staged) File.rename(staged, target) end |
.exec!(*args) ⇒ Object
Replaces the current process, so the binary's exit code and signal handling reach the caller (rake) unchanged.
114 115 116 |
# File 'lib/mcptask_rails_runner/binary.rb', line 114 def exec!(*args) Kernel.exec(path, *args) end |
.exec_installed!(*args) ⇒ Object
Installs the binary outside the gem first, then runs that copy.
Used by install and update, the two tasks that generate the scheduled job: the job runs whichever binary is executing them, so it has to be the copy at the version-free path rather than the one inside this gem.
123 124 125 |
# File 'lib/mcptask_rails_runner/binary.rb', line 123 def exec_installed!(*args) Kernel.exec(install!, *args) end |
.executable_name ⇒ Object
34 35 36 |
# File 'lib/mcptask_rails_runner/binary.rb', line 34 def executable_name Gem.win_platform? ? "mcptask_runner.exe" : "mcptask_runner" end |
.install!(install_dir: INSTALL_DIR, gem_root: GEM_ROOT, out: $stdout) ⇒ Object
Copies the bundled binary out of the gem, and returns what to run.
The gem bootstraps the binary; it does not host it. Everything the
scheduled job touches has to outlive bundle update, and a gem directory
does not — so install and update put a copy where nothing but they will
ever move it. A copy rather than a symlink: a link into the gem's libexec
dangles on the same bundle update, which is the identical 08:00 failure
moved one step along, and Windows wants a privilege for symlinks besides.
Teaching the launcher to run bundle exec rake instead would fix the path
and put bundler and Gemfile.lock back into the 08:00 path — the layer the
Go port set out to remove.
Newest wins, and says so. A host with several projects has several gem versions and one installed binary, so a project pinning an older gem must not quietly drag the machine's runner backwards.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/mcptask_rails_runner/binary.rb', line 88 def install!(install_dir: INSTALL_DIR, gem_root: GEM_ROOT, out: $stdout) override = ENV["MCPTASK_RUNNER_BIN"] unless override.nil? || override.empty? out.puts "[mcptask_runner] MCPTASK_RUNNER_BIN is set — running #{override} " \ "and leaving #{installed_path(install_dir: install_dir)} alone." return override end source = bundled_path(gem_root: gem_root) target = installed_path(install_dir: install_dir) existing = installed_version(target) if existing && existing > Gem::Version.new(VERSION) out.puts "[mcptask_runner] #{target} already holds #{existing}, which is newer than this " \ "gem's #{VERSION} — keeping it. Delete that file to install this gem's binary instead." return target end copy(source, target) out.puts "[mcptask_runner] installed mcptask_runner #{VERSION} into #{target}" \ "#{existing ? " (replacing #{existing})" : ''}." target end |
.installed_path(install_dir: INSTALL_DIR) ⇒ Object
The version-free path the installed copy lives at.
68 69 70 |
# File 'lib/mcptask_rails_runner/binary.rb', line 68 def installed_path(install_dir: INSTALL_DIR) File.join(install_dir, executable_name) end |
.installed_version(target) ⇒ Object
What the installed copy reports about itself, or nil when there is none — or when it cannot be asked, which a stray file at that path cannot be.
Asked of the binary rather than recorded in a marker file beside it: the binary is what actually runs at 08:00, and a marker can only be right about installs that went through here.
133 134 135 136 137 138 139 140 141 |
# File 'lib/mcptask_rails_runner/binary.rb', line 133 def installed_version(target) return nil unless File.file?(target) = IO.popen([target, "version"], err: File::NULL, &:read) raw = .to_s[VERSION_LINE, 1] raw && Gem::Version.new(raw) rescue StandardError nil end |
.path(gem_root: GEM_ROOT) ⇒ Object
Absolute path to the binary this gem runs. Never resolved through PATH: the binary shares the mcptask_runner name with the legacy gem's rake namespace, and a PATH lookup could pick up a stale or wrong install.
41 42 43 44 45 46 |
# File 'lib/mcptask_rails_runner/binary.rb', line 41 def path(gem_root: GEM_ROOT) override = ENV["MCPTASK_RUNNER_BIN"] return override unless override.nil? || override.empty? bundled_path(gem_root: gem_root) end |