kompo 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,427 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "open3"
6
+ require "time"
7
+
8
+ module Kompo
9
+ # Section to install static Ruby
10
+ # Switches between cache restore and building from source
11
+ # Required context:
12
+ # - ruby_version: Ruby version to build (default: current RUBY_VERSION)
13
+ # - kompo_cache: Cache directory for kompo (default: ~/.kompo/cache)
14
+ # - clear_cache: If true, clear the Ruby build cache before building
15
+ class InstallRuby < Taski::Section
16
+ interfaces :ruby_path, :bundler_path, :ruby_install_dir, :ruby_version,
17
+ :ruby_major_minor, :ruby_build_path, :original_ruby_install_dir
18
+
19
+ def impl
20
+ # Skip cache if --no-cache is specified, or if cache doesn't exist
21
+ return FromSource if Taski.args[:no_cache]
22
+
23
+ cache_exists? ? FromCache : FromSource
24
+ end
25
+
26
+ # Ruby extensions to build statically
27
+ STATIC_EXTENSIONS = %w[
28
+ bigdecimal
29
+ cgi/escape
30
+ continuation
31
+ coverage
32
+ date
33
+ digest/bubblebabble
34
+ digest
35
+ digest/md5
36
+ digest/rmd160
37
+ digest/sha1
38
+ digest/sha2
39
+ etc
40
+ fcntl
41
+ fiddle
42
+ io/console
43
+ io/nonblock
44
+ io/wait
45
+ json
46
+ json/generator
47
+ json/parser
48
+ nkf
49
+ monitor
50
+ objspace
51
+ openssl
52
+ pathname
53
+ psych
54
+ pty
55
+ racc/cparse
56
+ rbconfig/sizeof
57
+ readline
58
+ ripper
59
+ socket
60
+ stringio
61
+ strscan
62
+ syslog
63
+ zlib
64
+ ].freeze
65
+
66
+ # Restore Ruby from cache
67
+ # Uses the work_dir path saved in metadata to ensure $LOAD_PATH matches
68
+ class FromCache < Taski::Task
69
+ def run
70
+ @ruby_version = Taski.args.fetch(:ruby_version, RUBY_VERSION)
71
+ @ruby_major_minor = ruby_major_and_minor(@ruby_version)
72
+
73
+ kompo_cache = Taski.args.fetch(:kompo_cache, File.expand_path("~/.kompo/cache"))
74
+ version_cache_dir = File.join(kompo_cache, @ruby_version)
75
+ cache_install_dir = File.join(version_cache_dir, "ruby")
76
+
77
+ # Use WorkDir.path which now automatically uses cached work_dir path
78
+ work_dir = WorkDir.path
79
+
80
+ @ruby_install_dir = File.join(work_dir, "_ruby")
81
+ @ruby_path = File.join(@ruby_install_dir, "bin", "ruby")
82
+ @bundler_path = File.join(@ruby_install_dir, "bin", "bundler")
83
+ @ruby_build_path = File.join(@ruby_install_dir, "_build")
84
+ @original_ruby_install_dir = @ruby_install_dir
85
+
86
+ group("Restoring Ruby #{@ruby_version} from cache to #{work_dir}") do
87
+ # Clean up existing files in case work_dir is reused
88
+ FileUtils.rm_rf(@ruby_install_dir) if Dir.exist?(@ruby_install_dir)
89
+
90
+ # _build directory is included in cache_install_dir
91
+ FileUtils.cp_r(cache_install_dir, @ruby_install_dir)
92
+
93
+ puts "Restored from: #{version_cache_dir}"
94
+ end
95
+
96
+ # Fix shebangs in bin scripts to point to the new Ruby path
97
+ fix_bin_shebangs(@ruby_install_dir, @ruby_path)
98
+
99
+ # Fix ruby.pc prefix to point to the new install directory
100
+ fix_ruby_pc(@ruby_install_dir)
101
+
102
+ puts "Ruby #{@ruby_version} restored from cache"
103
+ version_output, = Open3.capture2(@ruby_path, "--version", err: File::NULL)
104
+ puts "Ruby version: #{version_output.chomp}"
105
+ end
106
+
107
+ def clean
108
+ return unless @ruby_install_dir && Dir.exist?(@ruby_install_dir)
109
+
110
+ FileUtils.rm_rf(@ruby_install_dir)
111
+ puts "Cleaned up Ruby installation"
112
+ end
113
+
114
+ private
115
+
116
+ def ruby_major_and_minor(version)
117
+ parts = version.split(".")
118
+ "#{parts[0]}.#{parts[1]}"
119
+ end
120
+
121
+ # Fix shebangs in bin directory to point to the correct Ruby path
122
+ def fix_bin_shebangs(ruby_install_dir, ruby_path)
123
+ bin_dir = File.join(ruby_install_dir, "bin")
124
+ return unless Dir.exist?(bin_dir)
125
+
126
+ Dir.glob(File.join(bin_dir, "*")).each do |bin_file|
127
+ next if File.directory?(bin_file)
128
+ next if File.basename(bin_file) == "ruby"
129
+
130
+ content = File.read(bin_file)
131
+ next unless content.start_with?("#!")
132
+
133
+ # Replace old ruby shebang with new one
134
+ # Handle both direct paths and env-style shebangs, preserving trailing args
135
+ new_content = content.sub(/^#!.*\bruby\b(.*)$/, "#!#{ruby_path}\\1")
136
+ File.write(bin_file, new_content) if new_content != content
137
+ end
138
+ end
139
+
140
+ # Fix ruby.pc prefix to match the current install directory
141
+ # This is necessary when restoring from cache because the cached ruby.pc
142
+ # still has the original build directory as prefix
143
+ def fix_ruby_pc(ruby_install_dir)
144
+ ruby_pc_path = File.join(ruby_install_dir, "lib", "pkgconfig", "ruby.pc")
145
+ return unless File.exist?(ruby_pc_path)
146
+
147
+ content = File.read(ruby_pc_path)
148
+ new_content = content.sub(/^prefix=.*$/, "prefix=#{ruby_install_dir}")
149
+ File.write(ruby_pc_path, new_content) if new_content != content
150
+ end
151
+ end
152
+
153
+ # Build Ruby from source using ruby-build
154
+ # Ruby is built into work_dir with --prefix=work_dir/_ruby.
155
+ # After building, the result is cached with the work_dir path preserved in metadata.
156
+ # When using cache, the same work_dir path is recreated to ensure $LOAD_PATH matches.
157
+ class FromSource < Taski::Task
158
+ def run
159
+ ruby_build = RubyBuildPath.path
160
+
161
+ @ruby_version = Taski.args.fetch(:ruby_version, RUBY_VERSION)
162
+ @ruby_major_minor = ruby_major_and_minor(@ruby_version)
163
+
164
+ @kompo_cache = Taski.args.fetch(:kompo_cache, File.expand_path("~/.kompo/cache"))
165
+ @version_cache_dir = File.join(@kompo_cache, @ruby_version)
166
+
167
+ # Check if we have a valid cache
168
+ cache_metadata_path = File.join(@version_cache_dir, "metadata.json")
169
+ if cache_valid?(cache_metadata_path)
170
+ restore_from_cache(cache_metadata_path)
171
+ else
172
+ build_and_cache(ruby_build)
173
+ end
174
+
175
+ puts "Ruby installed at: #{@ruby_install_dir}"
176
+ version_output, = Open3.capture2(@ruby_path, "--version", err: File::NULL)
177
+ puts "Ruby version: #{version_output.chomp}"
178
+ end
179
+
180
+ def clean
181
+ return unless @ruby_install_dir && Dir.exist?(@ruby_install_dir)
182
+
183
+ FileUtils.rm_rf(@ruby_install_dir)
184
+ puts "Cleaned up Ruby installation"
185
+ end
186
+
187
+ private
188
+
189
+ def cache_valid?(metadata_path)
190
+ return false unless File.exist?(metadata_path)
191
+
192
+ metadata = JSON.parse(File.read(metadata_path))
193
+ cached_work_dir = metadata["work_dir"]
194
+ cache_install_dir = File.join(@version_cache_dir, "ruby")
195
+
196
+ cached_work_dir && Dir.exist?(cache_install_dir)
197
+ rescue JSON::ParserError
198
+ false
199
+ end
200
+
201
+ def restore_from_cache(_metadata_path)
202
+ # Use WorkDir.path which now automatically uses cached work_dir path
203
+ work_dir = WorkDir.path
204
+
205
+ @ruby_install_dir = File.join(work_dir, "_ruby")
206
+ @ruby_path = File.join(@ruby_install_dir, "bin", "ruby")
207
+ @bundler_path = File.join(@ruby_install_dir, "bin", "bundler")
208
+ @ruby_build_path = File.join(@ruby_install_dir, "_build")
209
+ @original_ruby_install_dir = @ruby_install_dir
210
+
211
+ cache_install_dir = File.join(@version_cache_dir, "ruby")
212
+
213
+ group("Restoring Ruby from cache to #{work_dir}") do
214
+ # Clean up existing files in case work_dir is reused
215
+ FileUtils.rm_rf(@ruby_install_dir) if Dir.exist?(@ruby_install_dir)
216
+
217
+ # _build directory is included in cache_install_dir
218
+ FileUtils.cp_r(cache_install_dir, @ruby_install_dir)
219
+ end
220
+
221
+ # Fix shebangs in bin scripts to point to the new Ruby path
222
+ fix_bin_shebangs(@ruby_install_dir, @ruby_path)
223
+
224
+ # Fix ruby.pc prefix to point to the new install directory
225
+ fix_ruby_pc(@ruby_install_dir)
226
+ end
227
+
228
+ def build_and_cache(ruby_build)
229
+ work_dir = WorkDir.path
230
+
231
+ # Build Ruby into work_dir with prefix pointing to work_dir
232
+ @ruby_install_dir = File.join(work_dir, "_ruby")
233
+ @ruby_path = File.join(@ruby_install_dir, "bin", "ruby")
234
+ @bundler_path = File.join(@ruby_install_dir, "bin", "bundler")
235
+ @ruby_build_path = File.join(@ruby_install_dir, "_build")
236
+ @original_ruby_install_dir = @ruby_install_dir
237
+
238
+ # Handle custom Ruby source
239
+ ruby_source_path = Taski.args[:ruby_source_path]
240
+ ruby_definition = prepare_ruby_source(ruby_source_path)
241
+
242
+ # Check if the Ruby version is available in ruby-build (only if not using custom source)
243
+ check_ruby_version_availability(ruby_build) unless ruby_source_path
244
+
245
+ configure_opts = build_configure_opts(@ruby_install_dir)
246
+
247
+ command = [
248
+ ruby_build,
249
+ "--verbose",
250
+ "--keep",
251
+ ruby_definition,
252
+ @ruby_install_dir
253
+ ]
254
+
255
+ group("Building Ruby #{@ruby_version} in #{work_dir}") do
256
+ FileUtils.mkdir_p(@version_cache_dir)
257
+ # Pass configuration via environment variables required by ruby-build
258
+ env = {
259
+ "RUBY_CONFIGURE_OPTS" => configure_opts,
260
+ "RUBY_BUILD_CACHE_PATH" => @version_cache_dir,
261
+ "RUBY_BUILD_BUILD_PATH" => @ruby_build_path
262
+ }
263
+ # Clear Bundler environment to prevent interference with ruby-build
264
+ # This is necessary because ruby-build's make install runs rbinstall.rb,
265
+ # which loads rubygems, which loads bundler if BUNDLE_GEMFILE is set.
266
+ Bundler.with_unbundled_env do
267
+ system(env, *command) or raise "Failed to build Ruby"
268
+ end
269
+ end
270
+
271
+ # Save to cache after successful build
272
+ save_to_cache(work_dir)
273
+ end
274
+
275
+ def save_to_cache(work_dir)
276
+ cache_install_dir = File.join(@version_cache_dir, "ruby")
277
+
278
+ group("Saving Ruby to cache") do
279
+ # Remove old cache if exists
280
+ FileUtils.rm_rf(cache_install_dir) if Dir.exist?(cache_install_dir)
281
+
282
+ # Copy to cache
283
+ # Note: _build directory is included in @ruby_install_dir
284
+ FileUtils.cp_r(@ruby_install_dir, cache_install_dir)
285
+
286
+ # Save metadata with work_dir path
287
+ metadata = {
288
+ "work_dir" => work_dir,
289
+ "ruby_version" => @ruby_version,
290
+ "created_at" => Time.now.iso8601
291
+ }
292
+ File.write(File.join(@version_cache_dir, "metadata.json"), JSON.pretty_generate(metadata))
293
+ end
294
+ end
295
+
296
+ # Prepare Ruby source for building
297
+ # @param source_path [String, nil] Path to Ruby source (tarball or directory)
298
+ # @return [String] ruby-build definition (version or source path)
299
+ def prepare_ruby_source(source_path)
300
+ return @ruby_version unless source_path
301
+
302
+ raise "Ruby source path does not exist: #{source_path}" unless File.exist?(source_path)
303
+
304
+ if File.directory?(source_path)
305
+ # Directory: use it directly as ruby-build definition
306
+ puts "Using Ruby source directory: #{source_path}"
307
+ source_path
308
+ elsif source_path.end_with?(".tar.gz", ".tgz")
309
+ # Extract version from tarball filename
310
+ tarball_version = extract_version_from_tarball(source_path)
311
+
312
+ # Check for version mismatch if --ruby-version was explicitly specified
313
+ user_specified_version = Taski.args[:ruby_version]
314
+ if user_specified_version && tarball_version && user_specified_version != tarball_version
315
+ raise "Version mismatch: --ruby-version=#{user_specified_version} but tarball is ruby-#{tarball_version}.tar.gz. " \
316
+ "Please use matching versions or omit --ruby-version to use the tarball version."
317
+ end
318
+
319
+ # Use tarball version if available, otherwise fall back to @ruby_version
320
+ effective_version = tarball_version || @ruby_version
321
+ @ruby_version = effective_version
322
+ @ruby_major_minor = ruby_major_and_minor(effective_version)
323
+
324
+ # Update cache directory for new version
325
+ @version_cache_dir = File.join(@kompo_cache, effective_version)
326
+
327
+ # Tarball: copy to version cache directory with expected name for ruby-build
328
+ FileUtils.mkdir_p(@version_cache_dir)
329
+ target_tarball = File.join(@version_cache_dir, "ruby-#{effective_version}.tar.gz")
330
+ unless File.expand_path(source_path) == File.expand_path(target_tarball)
331
+ FileUtils.cp(source_path, target_tarball)
332
+ puts "Copied Ruby tarball to: #{target_tarball}"
333
+ end
334
+ puts "Using Ruby version from tarball: #{effective_version}"
335
+ effective_version
336
+ else
337
+ raise "Unsupported source format: #{source_path}. Expected .tar.gz file or directory."
338
+ end
339
+ end
340
+
341
+ # Extract Ruby version from tarball filename
342
+ # @param path [String] Path to tarball (e.g., /path/to/ruby-3.4.1.tar.gz)
343
+ # @return [String, nil] Version string or nil if not found
344
+ def extract_version_from_tarball(path)
345
+ filename = File.basename(path)
346
+ return unless filename =~ /^ruby-(\d+\.\d+\.\d+(?:-\w+)?)(?:\.tar)?\.(?:gz|tgz)$/
347
+
348
+ ::Regexp.last_match(1)
349
+ end
350
+
351
+ def build_configure_opts(install_dir)
352
+ [
353
+ "--prefix=#{install_dir}",
354
+ "--disable-install-doc",
355
+ "--disable-install-rdoc",
356
+ "--disable-install-capi",
357
+ "--with-static-linked-ext",
358
+ "--with-ruby-pc=ruby.pc",
359
+ "--with-ext=#{STATIC_EXTENSIONS.join(",")}",
360
+ "--disable-shared"
361
+ ].join(" ")
362
+ end
363
+
364
+ def check_ruby_version_availability(ruby_build)
365
+ output, status = Open3.capture2(ruby_build, "--definitions", err: File::NULL)
366
+ available_versions = status.success? ? output.split("\n").map(&:strip) : []
367
+
368
+ return if available_versions.include?(@ruby_version)
369
+
370
+ similar_versions = available_versions.select { |v| v.start_with?(@ruby_version.split(".")[0..1].join(".")) }
371
+ error_message = "Ruby #{@ruby_version} is not available in ruby-build.\n"
372
+ error_message += "Available similar versions: #{similar_versions.join(", ")}\n" unless similar_versions.empty?
373
+ error_message += "Try updating ruby-build or use --ruby-version to specify a different version."
374
+ raise error_message
375
+ end
376
+
377
+ def ruby_major_and_minor(version)
378
+ parts = version.split(".")
379
+ "#{parts[0]}.#{parts[1]}"
380
+ end
381
+
382
+ # Fix shebangs in bin directory to point to the correct Ruby path
383
+ def fix_bin_shebangs(ruby_install_dir, ruby_path)
384
+ bin_dir = File.join(ruby_install_dir, "bin")
385
+ return unless Dir.exist?(bin_dir)
386
+
387
+ Dir.glob(File.join(bin_dir, "*")).each do |bin_file|
388
+ next if File.directory?(bin_file)
389
+ next if File.basename(bin_file) == "ruby"
390
+
391
+ content = File.read(bin_file)
392
+ next unless content.start_with?("#!")
393
+
394
+ # Replace old ruby shebang with new one
395
+ # Handle both direct paths and env-style shebangs, preserving trailing args
396
+ new_content = content.sub(/^#!.*\bruby\b(.*)$/, "#!#{ruby_path}\\1")
397
+ File.write(bin_file, new_content) if new_content != content
398
+ end
399
+ end
400
+
401
+ # Fix ruby.pc prefix to match the current install directory
402
+ # This is necessary when restoring from cache because the cached ruby.pc
403
+ # still has the original build directory as prefix
404
+ def fix_ruby_pc(ruby_install_dir)
405
+ ruby_pc_path = File.join(ruby_install_dir, "lib", "pkgconfig", "ruby.pc")
406
+ return unless File.exist?(ruby_pc_path)
407
+
408
+ content = File.read(ruby_pc_path)
409
+ new_content = content.sub(/^prefix=.*$/, "prefix=#{ruby_install_dir}")
410
+ File.write(ruby_pc_path, new_content) if new_content != content
411
+ end
412
+ end
413
+
414
+ private
415
+
416
+ def cache_exists?
417
+ ruby_version = Taski.args.fetch(:ruby_version, RUBY_VERSION)
418
+ kompo_cache = Taski.args.fetch(:kompo_cache, File.expand_path("~/.kompo/cache"))
419
+ version_cache_dir = File.join(kompo_cache, ruby_version)
420
+
421
+ cache_install_dir = File.join(version_cache_dir, "ruby")
422
+ cache_metadata = File.join(version_cache_dir, "metadata.json")
423
+
424
+ Dir.exist?(cache_install_dir) && File.exist?(cache_metadata)
425
+ end
426
+ end
427
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require_relative 'kompo_vfs_version_check'
5
+
6
+ module Kompo
7
+ # Section to get the kompo-vfs library path.
8
+ # Priority:
9
+ # 1. Local directory (if specified via context[:local_kompo_vfs_path])
10
+ # 2. Homebrew (install if needed)
11
+ # 3. Source build (fallback if Homebrew doesn't support arch/os)
12
+ class KompoVfsPath < Taski::Section
13
+ interfaces :path
14
+
15
+ def impl
16
+ # Priority 1: Local directory if specified
17
+ return FromLocal if Taski.args[:local_kompo_vfs_path]
18
+
19
+ # # Priority 2: Homebrew if supported
20
+ return FromHomebrew if homebrew_supported?
21
+
22
+ # # Priority 3: Build from source
23
+ FromSource
24
+ end
25
+
26
+ # Build from local directory (requires Cargo)
27
+ class FromLocal < Taski::Task
28
+ def run
29
+ local_path = Taski.args[:local_kompo_vfs_path]
30
+ raise 'Local kompo-vfs path not specified' unless local_path
31
+ raise "Local kompo-vfs path does not exist: #{local_path}" unless Dir.exist?(local_path)
32
+
33
+ puts "Building kompo-vfs from local directory: #{local_path}"
34
+ cargo = CargoPath.path
35
+
36
+ raise 'Failed to build kompo-vfs' unless system(cargo, 'build', '--release', chdir: local_path)
37
+
38
+ @path = File.join(local_path, 'target', 'release')
39
+ puts "kompo-vfs library path: #{@path}"
40
+
41
+ KompoVfsVersionCheck.verify!(@path)
42
+ end
43
+ end
44
+
45
+ # Install via Homebrew (Section to handle installed vs not installed)
46
+ class FromHomebrew < Taski::Section
47
+ interfaces :path
48
+
49
+ def impl
50
+ kompo_vfs_installed? ? Installed : Install
51
+ end
52
+
53
+ # Use existing Homebrew installation of kompo-vfs
54
+ class Installed < Taski::Task
55
+ def run
56
+ brew = HomebrewPath.path
57
+ @path = "#{`#{brew} --prefix kompo-vfs`.chomp}/lib"
58
+
59
+ # Check if required library files exist (kompo-vfs >= 0.2.0 has libkompo_fs.a and libkompo_wrap.a)
60
+ required_libs = %w[libkompo_fs.a libkompo_wrap.a]
61
+ missing_libs = required_libs.reject { |lib| File.exist?(File.join(@path, lib)) }
62
+
63
+ unless missing_libs.empty?
64
+ installed_version = `#{brew} list --versions kompo-vfs`.chomp.split.last
65
+ raise "kompo-vfs #{installed_version} is outdated. Please run: brew upgrade kompo-vfs\n" \
66
+ "Missing libraries: #{missing_libs.join(', ')}"
67
+ end
68
+
69
+ puts "kompo-vfs library path: #{@path}"
70
+
71
+ KompoVfsVersionCheck.verify!(@path)
72
+ end
73
+ end
74
+
75
+ # Install kompo-vfs via Homebrew
76
+ class Install < Taski::Task
77
+ def run
78
+ brew = HomebrewPath.path
79
+ puts 'Installing kompo-vfs via Homebrew...'
80
+ system(brew, 'tap', 'ahogappa/kompo') or raise 'Failed to tap ahogappa/kompo'
81
+ system(brew, 'install', 'kompo-vfs') or raise 'Failed to install kompo-vfs'
82
+
83
+ @path = "#{`#{brew} --prefix kompo-vfs`.chomp}/lib"
84
+ puts "kompo-vfs library path: #{@path}"
85
+
86
+ KompoVfsVersionCheck.verify!(@path)
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def kompo_vfs_installed?
93
+ # Avoid calling HomebrewPath.path here to prevent duplicate dependency
94
+ brew = `which brew 2>/dev/null`.chomp
95
+ return false if brew.empty?
96
+
97
+ system("#{brew} list kompo-vfs > /dev/null 2>&1")
98
+ end
99
+ end
100
+
101
+ # Build from source (requires Cargo)
102
+ class FromSource < Taski::Task
103
+ REPO_URL = 'https://github.com/ahogappa/kompo-vfs'
104
+
105
+ def run
106
+ puts 'Building kompo-vfs from source...'
107
+ cargo = CargoPath.path
108
+
109
+ build_dir = File.expand_path('~/.kompo/kompo-vfs')
110
+ FileUtils.mkdir_p(File.dirname(build_dir))
111
+
112
+ if Dir.exist?(build_dir)
113
+ system('git', '-C', build_dir, 'pull', '--quiet')
114
+ else
115
+ system('git', 'clone', REPO_URL, build_dir) or raise 'Failed to clone kompo-vfs repository'
116
+ end
117
+
118
+ system(cargo, 'build', '--release', chdir: build_dir) or raise 'Failed to build kompo-vfs'
119
+
120
+ @path = File.join(build_dir, 'target', 'release')
121
+ puts "kompo-vfs library path: #{@path}"
122
+
123
+ KompoVfsVersionCheck.verify!(@path)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def homebrew_supported?
130
+ # Check if current arch/os combination is supported by Homebrew formula
131
+ arch = `uname -m`.chomp
132
+ os = `uname -s`.chomp
133
+
134
+ # Supported combinations (adjust based on actual formula support)
135
+ supported = [
136
+ %w[arm64 Darwin],
137
+ %w[x86_64 Darwin],
138
+ %w[x86_64 Linux]
139
+ ]
140
+
141
+ supported.include?([arch, os])
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../version'
4
+
5
+ module Kompo
6
+ # Verifies that the installed kompo-vfs version meets minimum requirements.
7
+ # Checks the KOMPO_VFS_VERSION file in the library directory.
8
+ module KompoVfsVersionCheck
9
+ class IncompatibleVersionError < StandardError; end
10
+
11
+ VERSION_FILE = 'KOMPO_VFS_VERSION'
12
+
13
+ def self.verify!(lib_path)
14
+ actual_version = get_version(lib_path)
15
+ required_version = Kompo::KOMPO_VFS_MIN_VERSION
16
+
17
+ return if version_satisfies?(actual_version, required_version)
18
+
19
+ raise IncompatibleVersionError, build_error_message(actual_version, required_version)
20
+ end
21
+
22
+ def self.get_version(lib_path)
23
+ version_file = File.join(lib_path, VERSION_FILE)
24
+
25
+ raise IncompatibleVersionError, build_missing_file_message(version_file) unless File.exist?(version_file)
26
+
27
+ File.read(version_file).strip
28
+ end
29
+
30
+ def self.version_satisfies?(actual, required)
31
+ Gem::Version.new(actual) >= Gem::Version.new(required)
32
+ end
33
+
34
+ def self.build_error_message(actual_version, required_version)
35
+ <<~MSG.chomp
36
+ kompo-vfs version #{actual_version} is too old.
37
+ Required: >= #{required_version}
38
+
39
+ Please upgrade:
40
+ Homebrew: brew upgrade kompo-vfs
41
+ Source: cd ~/.kompo/kompo-vfs && git pull && cargo build --release
42
+ MSG
43
+ end
44
+
45
+ def self.build_missing_file_message(version_file)
46
+ <<~MSG.chomp
47
+ kompo-vfs version file not found at: #{version_file}
48
+ Your kompo-vfs installation may be outdated (< 0.5.0).
49
+
50
+ Please upgrade:
51
+ Homebrew: brew upgrade kompo-vfs
52
+ Source: cd ~/.kompo/kompo-vfs && git pull && cargo build --release
53
+ MSG
54
+ end
55
+ end
56
+ end