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.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +2 -4
- data/.devcontainer/devcontainer.json +2 -2
- data/.standard.yml +2 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +10 -2
- data/Gemfile.lock +115 -3
- data/README.md +117 -10
- data/Rakefile +12 -2
- data/docs/document.md.ja +31 -0
- data/exe/kompo +45 -14
- data/lib/fs.c.erb +6 -0
- data/lib/kompo/cache.rb +65 -0
- data/lib/kompo/kompo_ignore.rb +40 -0
- data/lib/kompo/tasks/build_native_gem.rb +191 -0
- data/lib/kompo/tasks/bundle_install.rb +224 -0
- data/lib/kompo/tasks/cargo_path.rb +59 -0
- data/lib/kompo/tasks/check_stdlibs.rb +58 -0
- data/lib/kompo/tasks/collect_dependencies.rb +101 -0
- data/lib/kompo/tasks/copy_gemfile.rb +46 -0
- data/lib/kompo/tasks/copy_project_files.rb +89 -0
- data/lib/kompo/tasks/find_native_extensions.rb +89 -0
- data/lib/kompo/tasks/homebrew.rb +83 -0
- data/lib/kompo/tasks/install_deps.rb +365 -0
- data/lib/kompo/tasks/install_ruby.rb +427 -0
- data/lib/kompo/tasks/kompo_vfs_path.rb +144 -0
- data/lib/kompo/tasks/kompo_vfs_version_check.rb +56 -0
- data/lib/kompo/tasks/make_fs_c.rb +202 -0
- data/lib/kompo/tasks/make_main_c.rb +65 -0
- data/lib/kompo/tasks/packing.rb +235 -0
- data/lib/kompo/tasks/ruby_build_path.rb +54 -0
- data/lib/kompo/tasks/work_dir.rb +84 -0
- data/lib/kompo/version.rb +2 -1
- data/lib/kompo.rb +47 -420
- data/lib/main.c.erb +28 -15
- data/rbs_collection.lock.yaml +116 -0
- data/rbs_collection.yaml +19 -0
- metadata +72 -8
- data/lib/kompo/kompo_fs.rb +0 -15
|
@@ -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
|