konpeito 0.3.0 → 0.3.1
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/lib/konpeito/cache/run_cache.rb +137 -0
- data/lib/konpeito/cache.rb +1 -0
- data/lib/konpeito/cli/completion_command.rb +6 -1
- data/lib/konpeito/cli/run_command.rb +105 -14
- data/lib/konpeito/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1fe9ef9f2b988dc2d9751cecb6f53c2828e1e0d412cecc897557d7988e967b48
|
|
4
|
+
data.tar.gz: 68b6e211a60e31b48b61858b62902229b0980e786147b2e4427688f169c0caea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9dbf1f9e6edffb5e9e91af5398620fa96218f2ddde0cddebfa31ab4b90a42c0206cc71d24a6e2035dfd34f3695b7ba5265aef606c91af1b3cdee430609f537de
|
|
7
|
+
data.tar.gz: 7e595a3ec330e9f359bd5dc412b7d3c9fe9e9a3bef0fde8632f7b35fbe4f9902b174a93a25b11a5ea1e5364983f4ec8d1a92ff3e78a0aa5ebeeaf015bf2dd821
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module Konpeito
|
|
8
|
+
module Cache
|
|
9
|
+
# Manages compilation cache for `konpeito run`.
|
|
10
|
+
# Content-addressed: cache key is SHA256 of all inputs.
|
|
11
|
+
class RunCache
|
|
12
|
+
MANIFEST_FILE = "run_manifest.json"
|
|
13
|
+
DEFAULT_MAX_ENTRIES = 20
|
|
14
|
+
|
|
15
|
+
attr_reader :cache_dir
|
|
16
|
+
|
|
17
|
+
def initialize(cache_dir: ".konpeito_cache/run")
|
|
18
|
+
@cache_dir = File.expand_path(cache_dir)
|
|
19
|
+
@manifest = nil
|
|
20
|
+
load_manifest
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Compute a cache key from all compilation inputs.
|
|
24
|
+
def compute_cache_key(source_files:, rbs_files:, options_hash:)
|
|
25
|
+
digest = Digest::SHA256.new
|
|
26
|
+
source_files.sort.each { |f| digest.update(Digest::SHA256.file(f).hexdigest) }
|
|
27
|
+
rbs_files.sort.each { |f| digest.update(Digest::SHA256.file(f).hexdigest) }
|
|
28
|
+
digest.update(options_hash.sort.map { |k, v| "#{k}=#{v}" }.join("|"))
|
|
29
|
+
digest.update(Konpeito::VERSION)
|
|
30
|
+
digest.hexdigest
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Look up a cached artifact. Returns the path if it exists, nil otherwise.
|
|
34
|
+
def lookup(cache_key, basename)
|
|
35
|
+
entry = @manifest["entries"][cache_key]
|
|
36
|
+
return nil unless entry
|
|
37
|
+
|
|
38
|
+
artifact = artifact_path(cache_key, basename)
|
|
39
|
+
return nil unless File.exist?(artifact)
|
|
40
|
+
|
|
41
|
+
# Update last_used_at
|
|
42
|
+
entry["last_used_at"] = Time.now.iso8601
|
|
43
|
+
save_manifest
|
|
44
|
+
artifact
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Register a compiled artifact in the manifest.
|
|
48
|
+
# The artifact should already exist at artifact_dir(cache_key)/basename.
|
|
49
|
+
def store(cache_key, basename)
|
|
50
|
+
artifact = artifact_path(cache_key, basename)
|
|
51
|
+
return nil unless File.exist?(artifact)
|
|
52
|
+
|
|
53
|
+
@manifest["entries"][cache_key] = {
|
|
54
|
+
"basename" => basename,
|
|
55
|
+
"created_at" => Time.now.iso8601,
|
|
56
|
+
"last_used_at" => Time.now.iso8601
|
|
57
|
+
}
|
|
58
|
+
save_manifest
|
|
59
|
+
cleanup!
|
|
60
|
+
artifact
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Directory for a given cache key's artifact.
|
|
64
|
+
def artifact_dir(cache_key)
|
|
65
|
+
File.join(@cache_dir, cache_key)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Full path to the artifact file.
|
|
69
|
+
def artifact_path(cache_key, basename)
|
|
70
|
+
File.join(artifact_dir(cache_key), basename)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Remove all cached entries.
|
|
74
|
+
def clean!
|
|
75
|
+
FileUtils.rm_rf(@cache_dir)
|
|
76
|
+
FileUtils.mkdir_p(@cache_dir)
|
|
77
|
+
@manifest = create_empty_manifest
|
|
78
|
+
save_manifest
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Evict oldest entries beyond max_entries.
|
|
82
|
+
def cleanup!(max_entries: DEFAULT_MAX_ENTRIES)
|
|
83
|
+
entries = @manifest["entries"]
|
|
84
|
+
return if entries.size <= max_entries
|
|
85
|
+
|
|
86
|
+
# Sort by last_used_at ascending (oldest first)
|
|
87
|
+
sorted = entries.sort_by { |_k, v| v["last_used_at"] || v["created_at"] || "" }
|
|
88
|
+
to_remove = sorted.first(entries.size - max_entries)
|
|
89
|
+
|
|
90
|
+
to_remove.each do |key, _|
|
|
91
|
+
dir = artifact_dir(key)
|
|
92
|
+
FileUtils.rm_rf(dir) if Dir.exist?(dir)
|
|
93
|
+
entries.delete(key)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
save_manifest
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def load_manifest
|
|
102
|
+
FileUtils.mkdir_p(@cache_dir)
|
|
103
|
+
manifest_path = File.join(@cache_dir, MANIFEST_FILE)
|
|
104
|
+
|
|
105
|
+
if File.exist?(manifest_path)
|
|
106
|
+
begin
|
|
107
|
+
@manifest = JSON.parse(File.read(manifest_path))
|
|
108
|
+
# Ensure entries key exists
|
|
109
|
+
@manifest["entries"] ||= {}
|
|
110
|
+
rescue JSON::ParserError
|
|
111
|
+
@manifest = create_empty_manifest
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
@manifest = create_empty_manifest
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def save_manifest
|
|
119
|
+
manifest_path = File.join(@cache_dir, MANIFEST_FILE)
|
|
120
|
+
# Write to tmp file then rename for atomicity
|
|
121
|
+
tmp_path = "#{manifest_path}.tmp.#{Process.pid}"
|
|
122
|
+
File.write(tmp_path, JSON.pretty_generate(@manifest))
|
|
123
|
+
File.rename(tmp_path, manifest_path)
|
|
124
|
+
rescue StandardError
|
|
125
|
+
# Best effort — don't crash if manifest write fails
|
|
126
|
+
FileUtils.rm_f(tmp_path) if tmp_path
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def create_empty_manifest
|
|
130
|
+
{
|
|
131
|
+
"version" => Konpeito::VERSION,
|
|
132
|
+
"entries" => {}
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
data/lib/konpeito/cache.rb
CHANGED
|
@@ -102,7 +102,7 @@ module Konpeito
|
|
|
102
102
|
;;
|
|
103
103
|
run)
|
|
104
104
|
if [[ "${cur}" == -* ]]; then
|
|
105
|
-
COMPREPLY=( $(compgen -W "--target --classpath --rbs -I --require-path --inline -v --verbose --no-color -h --help" -- "${cur}") )
|
|
105
|
+
COMPREPLY=( $(compgen -W "--target --classpath --rbs -I --require-path --inline --no-cache --clean-run-cache -v --verbose --no-color -h --help" -- "${cur}") )
|
|
106
106
|
else
|
|
107
107
|
COMPREPLY=( $(compgen -f -X '!*.rb' -- "${cur}") )
|
|
108
108
|
fi
|
|
@@ -191,6 +191,8 @@ module Konpeito
|
|
|
191
191
|
'-I[Add require search path]:path:_directories' \
|
|
192
192
|
'--require-path[Add require search path]:path:_directories' \
|
|
193
193
|
'--inline[Use inline RBS annotations]' \
|
|
194
|
+
'--no-cache[Force recompilation]' \
|
|
195
|
+
'--clean-run-cache[Clear run cache before building]' \
|
|
194
196
|
'-v[Verbose output]' \
|
|
195
197
|
'--verbose[Verbose output]' \
|
|
196
198
|
'--no-color[Disable colored output]' \
|
|
@@ -278,6 +280,9 @@ module Konpeito
|
|
|
278
280
|
complete -c konpeito -n '__fish_seen_subcommand_from run' -l classpath -r -d 'JVM classpath'
|
|
279
281
|
complete -c konpeito -n '__fish_seen_subcommand_from run' -l rbs -r -d 'RBS type definition file'
|
|
280
282
|
complete -c konpeito -n '__fish_seen_subcommand_from run' -s I -l require-path -r -d 'Add require search path'
|
|
283
|
+
complete -c konpeito -n '__fish_seen_subcommand_from run' -l inline -d 'Use inline RBS annotations'
|
|
284
|
+
complete -c konpeito -n '__fish_seen_subcommand_from run' -l no-cache -d 'Force recompilation'
|
|
285
|
+
complete -c konpeito -n '__fish_seen_subcommand_from run' -l clean-run-cache -d 'Clear run cache'
|
|
281
286
|
complete -c konpeito -n '__fish_seen_subcommand_from run' -s v -l verbose -d 'Verbose output'
|
|
282
287
|
complete -c konpeito -n '__fish_seen_subcommand_from run' -l no-color -d 'Disable colored output'
|
|
283
288
|
complete -c konpeito -n '__fish_seen_subcommand_from run' -F -d 'Ruby source file'
|
|
@@ -49,7 +49,9 @@ module Konpeito
|
|
|
49
49
|
rbs_paths: config.rbs_paths.dup,
|
|
50
50
|
require_paths: config.require_paths.dup,
|
|
51
51
|
inline_rbs: false,
|
|
52
|
-
lib: false
|
|
52
|
+
lib: false,
|
|
53
|
+
no_cache: false,
|
|
54
|
+
clean_run_cache: false
|
|
53
55
|
}
|
|
54
56
|
end
|
|
55
57
|
|
|
@@ -74,6 +76,14 @@ module Konpeito
|
|
|
74
76
|
options[:inline_rbs] = true
|
|
75
77
|
end
|
|
76
78
|
|
|
79
|
+
opts.on("--no-cache", "Force recompilation (skip run cache)") do
|
|
80
|
+
options[:no_cache] = true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
opts.on("--clean-run-cache", "Clear the run cache before building") do
|
|
84
|
+
options[:clean_run_cache] = true
|
|
85
|
+
end
|
|
86
|
+
|
|
77
87
|
super
|
|
78
88
|
end
|
|
79
89
|
|
|
@@ -82,7 +92,9 @@ module Konpeito
|
|
|
82
92
|
Usage: konpeito run [options] [source.rb]
|
|
83
93
|
|
|
84
94
|
Examples:
|
|
85
|
-
konpeito run src/main.rb Build and run (native)
|
|
95
|
+
konpeito run src/main.rb Build and run (native, cached)
|
|
96
|
+
konpeito run --no-cache src/main.rb Force recompilation
|
|
97
|
+
konpeito run --clean-run-cache src/main.rb Clear cache, then build and run
|
|
86
98
|
konpeito run --inline src/main.rb Build and run with inline RBS
|
|
87
99
|
konpeito run --target jvm src/main.rb Build and run (JVM)
|
|
88
100
|
BANNER
|
|
@@ -108,14 +120,101 @@ module Konpeito
|
|
|
108
120
|
end
|
|
109
121
|
|
|
110
122
|
def run_native(source_file)
|
|
123
|
+
require "konpeito/cache"
|
|
124
|
+
|
|
125
|
+
basename = "#{File.basename(source_file, '.rb')}#{Platform.shared_lib_extension}"
|
|
126
|
+
run_cache = Cache::RunCache.new
|
|
127
|
+
|
|
128
|
+
if options[:clean_run_cache]
|
|
129
|
+
run_cache.clean!
|
|
130
|
+
emit("Cleaned", "run cache")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if options[:no_cache]
|
|
134
|
+
build_and_run_tmpdir(source_file, basename)
|
|
135
|
+
return
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Compute cache key once (runs DependencyResolver)
|
|
139
|
+
cache_key = compute_run_cache_key(source_file, run_cache)
|
|
140
|
+
|
|
141
|
+
# Try cache hit
|
|
142
|
+
if cache_key
|
|
143
|
+
artifact = run_cache.lookup(cache_key, basename)
|
|
144
|
+
if artifact
|
|
145
|
+
emit("Cached", source_file)
|
|
146
|
+
emit("Running", artifact)
|
|
147
|
+
run_without_bundler("ruby", "-r", artifact, "-e", "")
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Cache miss: build into cache dir
|
|
153
|
+
if cache_key
|
|
154
|
+
build_and_run_cached(source_file, run_cache, cache_key, basename)
|
|
155
|
+
else
|
|
156
|
+
build_and_run_tmpdir(source_file, basename)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def compute_run_cache_key(source_file, run_cache)
|
|
161
|
+
resolver = DependencyResolver.new(
|
|
162
|
+
base_paths: options[:require_paths],
|
|
163
|
+
verbose: false
|
|
164
|
+
)
|
|
165
|
+
resolver.resolve(source_file)
|
|
166
|
+
|
|
167
|
+
all_sources = resolver.resolved_files.keys
|
|
168
|
+
auto_rbs = resolver.rbs_paths
|
|
169
|
+
all_rbs = (options[:rbs_paths].map { |p| File.expand_path(p) } + auto_rbs).uniq
|
|
170
|
+
all_rbs = all_rbs.select { |f| File.exist?(f) }
|
|
171
|
+
|
|
172
|
+
options_hash = {
|
|
173
|
+
"inline_rbs" => options[:inline_rbs].to_s,
|
|
174
|
+
"target" => "native"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
run_cache.compute_cache_key(
|
|
178
|
+
source_files: all_sources,
|
|
179
|
+
rbs_files: all_rbs,
|
|
180
|
+
options_hash: options_hash
|
|
181
|
+
)
|
|
182
|
+
rescue StandardError => e
|
|
183
|
+
puts_verbose("Cache key computation failed: #{e.message}")
|
|
184
|
+
nil
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def build_and_run_cached(source_file, run_cache, cache_key, basename)
|
|
188
|
+
dir = run_cache.artifact_dir(cache_key)
|
|
189
|
+
FileUtils.mkdir_p(dir)
|
|
190
|
+
output_file = File.join(dir, basename)
|
|
191
|
+
|
|
192
|
+
build_args = build_native_args(source_file, output_file)
|
|
193
|
+
Commands::BuildCommand.new(build_args, config: config).run
|
|
194
|
+
|
|
195
|
+
run_cache.store(cache_key, basename)
|
|
196
|
+
|
|
197
|
+
emit("Running", output_file)
|
|
198
|
+
run_without_bundler("ruby", "-r", output_file, "-e", "")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def build_and_run_tmpdir(source_file, basename)
|
|
111
202
|
require "tmpdir"
|
|
112
203
|
|
|
113
|
-
# Use a subdirectory to avoid conflicts, but keep the basename matching
|
|
114
|
-
# the Init_ function name (Ruby requires Init_<basename> to match the filename)
|
|
115
204
|
@run_tmpdir = File.join(Dir.tmpdir, "konpeito_run_#{Process.pid}")
|
|
116
205
|
FileUtils.mkdir_p(@run_tmpdir)
|
|
117
|
-
output_file = File.join(@run_tmpdir,
|
|
206
|
+
output_file = File.join(@run_tmpdir, basename)
|
|
207
|
+
|
|
208
|
+
build_args = build_native_args(source_file, output_file)
|
|
209
|
+
Commands::BuildCommand.new(build_args, config: config).run
|
|
210
|
+
|
|
211
|
+
emit("Running", output_file)
|
|
212
|
+
run_without_bundler("ruby", "-r", output_file, "-e", "")
|
|
213
|
+
ensure
|
|
214
|
+
FileUtils.rm_rf(@run_tmpdir) if @run_tmpdir && Dir.exist?(@run_tmpdir)
|
|
215
|
+
end
|
|
118
216
|
|
|
217
|
+
def build_native_args(source_file, output_file)
|
|
119
218
|
build_args = ["-o", output_file]
|
|
120
219
|
build_args << "-v" if options[:verbose]
|
|
121
220
|
build_args << "--no-color" unless options[:color]
|
|
@@ -123,15 +222,7 @@ module Konpeito
|
|
|
123
222
|
options[:rbs_paths].each { |p| build_args << "--rbs" << p }
|
|
124
223
|
options[:require_paths].each { |p| build_args << "-I" << p }
|
|
125
224
|
build_args << source_file
|
|
126
|
-
|
|
127
|
-
Commands::BuildCommand.new(build_args, config: config).run
|
|
128
|
-
|
|
129
|
-
emit("Running", output_file)
|
|
130
|
-
# Run without Bundler environment so the compiled extension can load
|
|
131
|
-
# any installed gem (not just those in the current Gemfile)
|
|
132
|
-
run_without_bundler("ruby", "-r", output_file, "-e", "")
|
|
133
|
-
ensure
|
|
134
|
-
FileUtils.rm_rf(@run_tmpdir) if @run_tmpdir && Dir.exist?(@run_tmpdir)
|
|
225
|
+
build_args
|
|
135
226
|
end
|
|
136
227
|
|
|
137
228
|
def build_classpath
|
data/lib/konpeito/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: konpeito
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yasushi Itoh
|
|
@@ -64,6 +64,7 @@ files:
|
|
|
64
64
|
- lib/konpeito/cache.rb
|
|
65
65
|
- lib/konpeito/cache/cache_manager.rb
|
|
66
66
|
- lib/konpeito/cache/dependency_graph.rb
|
|
67
|
+
- lib/konpeito/cache/run_cache.rb
|
|
67
68
|
- lib/konpeito/cli.rb
|
|
68
69
|
- lib/konpeito/cli/base_command.rb
|
|
69
70
|
- lib/konpeito/cli/build_command.rb
|