ruby_workspace_manager 0.6.4 → 0.6.5
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/rwm/affected_detector.rb +1 -1
- data/lib/rwm/commands/bootstrap.rb +9 -7
- data/lib/rwm/dependency_graph.rb +3 -4
- data/lib/rwm/errors.rb +14 -0
- data/lib/rwm/gemfile_parser.rb +5 -1
- data/lib/rwm/task_cache.rb +53 -21
- data/lib/rwm/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 115ec9332203fbf44622ec26f179450eb6dcabce1bb9ad7a523bd6cd470b7cdd
|
|
4
|
+
data.tar.gz: 4d0c0fc8898f786091a86634408b018a236bcfcb772589b30e917bc5d6ece7c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b0a671c6a7b4e2a66f6120f7a2c635e52e2c9715312fe2b576a298a89878be4ee66f38758e3cbeb1fd865e7c5083ffd10e936d1041fabc10125e807e218b7947
|
|
7
|
+
data.tar.gz: a787011658b8f986c0b2353fffc97938978113cfd9e549f6d6a8287034a5ae575456661507dde36a4a01e7fc05957bcedb8db381bdc4ef5acb2f925782d874a0
|
|
@@ -62,7 +62,7 @@ module Rwm
|
|
|
62
62
|
_, _, status = Open3.capture3("git", "-C", workspace.root, "rev-parse", "--verify", "#{@base_branch}^{commit}")
|
|
63
63
|
return if status.success?
|
|
64
64
|
|
|
65
|
-
raise Rwm::
|
|
65
|
+
raise Rwm::InvalidBaseRefError, @base_branch
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def detect_base_branch
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
3
5
|
module Rwm
|
|
4
6
|
module Commands
|
|
5
7
|
class Bootstrap
|
|
@@ -140,8 +142,8 @@ module Rwm
|
|
|
140
142
|
return unless File.exist?(File.join(dir, "Gemfile"))
|
|
141
143
|
|
|
142
144
|
puts " bundle install..."
|
|
143
|
-
|
|
144
|
-
unless success
|
|
145
|
+
_, _, status = Open3.capture3(Rwm.bundle_env(dir), "bundle", "install", chdir: dir)
|
|
146
|
+
unless status.success?
|
|
145
147
|
raise BootstrapError, "bundle install failed in #{dir}"
|
|
146
148
|
end
|
|
147
149
|
end
|
|
@@ -150,13 +152,13 @@ module Rwm
|
|
|
150
152
|
return unless File.exist?(File.join(dir, "Rakefile"))
|
|
151
153
|
|
|
152
154
|
# Check if bootstrap task exists before running it
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return unless
|
|
155
|
+
_, _, status = Open3.capture3(Rwm.bundle_env(dir), "bundle", "exec", "rake", "-s", "-T", "bootstrap",
|
|
156
|
+
chdir: dir)
|
|
157
|
+
return unless status.success?
|
|
156
158
|
|
|
157
159
|
puts " rake bootstrap..."
|
|
158
|
-
|
|
159
|
-
unless success
|
|
160
|
+
_, _, status = Open3.capture3(Rwm.bundle_env(dir), "bundle", "exec", "rake", "bootstrap", chdir: dir)
|
|
161
|
+
unless status.success?
|
|
160
162
|
raise BootstrapError, "rake bootstrap failed in #{dir}"
|
|
161
163
|
end
|
|
162
164
|
end
|
data/lib/rwm/dependency_graph.rb
CHANGED
|
@@ -187,22 +187,21 @@ module Rwm
|
|
|
187
187
|
end
|
|
188
188
|
|
|
189
189
|
# Serialize to JSON for .rwm/graph.json
|
|
190
|
-
def to_json_data
|
|
190
|
+
def to_json_data(workspace_root: "")
|
|
191
191
|
{
|
|
192
192
|
"version" => 1,
|
|
193
193
|
"generated_at" => Time.now.iso8601,
|
|
194
194
|
"packages" => @packages.transform_values do |pkg|
|
|
195
|
-
{ "name" => pkg.name, "type" => pkg.type.to_s, "path" => pkg.relative_path(
|
|
195
|
+
{ "name" => pkg.name, "type" => pkg.type.to_s, "path" => pkg.relative_path(workspace_root) }
|
|
196
196
|
end,
|
|
197
197
|
"edges" => @edges.transform_values(&:sort)
|
|
198
198
|
}
|
|
199
199
|
end
|
|
200
200
|
|
|
201
201
|
def save(path, workspace_root)
|
|
202
|
-
@workspace_root = workspace_root
|
|
203
202
|
dir = File.dirname(path)
|
|
204
203
|
FileUtils.mkdir_p(dir)
|
|
205
|
-
write_locked(path, JSON.pretty_generate(to_json_data) + "\n")
|
|
204
|
+
write_locked(path, JSON.pretty_generate(to_json_data(workspace_root: workspace_root)) + "\n")
|
|
206
205
|
end
|
|
207
206
|
|
|
208
207
|
def to_dot
|
data/lib/rwm/errors.rb
CHANGED
|
@@ -40,4 +40,18 @@ module Rwm
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
class BootstrapError < Error; end
|
|
43
|
+
|
|
44
|
+
class InvalidBaseRefError < Error
|
|
45
|
+
def initialize(ref)
|
|
46
|
+
super("Base ref '#{ref}' does not exist. Check the branch name or pass a valid --base ref.")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class GemfileParseError < Error
|
|
51
|
+
def initialize(path, cause_message)
|
|
52
|
+
super("Failed to parse Gemfile at #{path}: #{cause_message}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class CacheError < Error; end
|
|
43
57
|
end
|
data/lib/rwm/gemfile_parser.rb
CHANGED
|
@@ -17,7 +17,11 @@ module Rwm
|
|
|
17
17
|
|
|
18
18
|
def parse
|
|
19
19
|
dsl = Bundler::Dsl.new
|
|
20
|
-
|
|
20
|
+
begin
|
|
21
|
+
dsl.eval_gemfile(@gemfile_path)
|
|
22
|
+
rescue Bundler::GemfileError, SyntaxError => e
|
|
23
|
+
raise Rwm::GemfileParseError.new(@gemfile_path, e.message)
|
|
24
|
+
end
|
|
21
25
|
deps = dsl.dependencies
|
|
22
26
|
|
|
23
27
|
gemfile_dir = File.expand_path(File.dirname(@gemfile_path))
|
data/lib/rwm/task_cache.rb
CHANGED
|
@@ -8,6 +8,11 @@ require "open3"
|
|
|
8
8
|
|
|
9
9
|
module Rwm
|
|
10
10
|
class TaskCache
|
|
11
|
+
# Salt: bump when the hashing scheme changes (file ordering, what gets
|
|
12
|
+
# included, normalisation rules) so existing caches become misses rather
|
|
13
|
+
# than wrong hits.
|
|
14
|
+
CACHE_HASH_VERSION = "v1"
|
|
15
|
+
|
|
11
16
|
def self.clean(workspace, package_name: nil)
|
|
12
17
|
cache_dir = File.join(workspace.root, ".rwm", "cache")
|
|
13
18
|
return unless Dir.exist?(cache_dir)
|
|
@@ -77,33 +82,27 @@ module Rwm
|
|
|
77
82
|
!matches.empty?
|
|
78
83
|
end
|
|
79
84
|
|
|
80
|
-
# Compute a content hash for a package: SHA256 of all source files + dependency hashes
|
|
85
|
+
# Compute a content hash for a package: SHA256 of all source files + dependency hashes.
|
|
86
|
+
# Walks `package` and its transitive deps in topological order (deps before dependents)
|
|
87
|
+
# so each dep's hash is memoised before any package that depends on it is hashed —
|
|
88
|
+
# avoids the unbounded recursion of the natural recursive formulation on deep chains.
|
|
81
89
|
def content_hash(package)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
end
|
|
90
|
+
cached = read_memoised_hash(package.name)
|
|
91
|
+
return cached if cached
|
|
85
92
|
|
|
86
|
-
|
|
93
|
+
needed = @graph.transitive_dependencies(package.name).to_set
|
|
94
|
+
needed << package.name
|
|
87
95
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
digest.update(rel_path)
|
|
92
|
-
digest.update(File.read(file))
|
|
93
|
-
end
|
|
96
|
+
@graph.topological_order.each do |name|
|
|
97
|
+
next unless needed.include?(name)
|
|
98
|
+
next if read_memoised_hash(name)
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@graph.dependencies(package.name).sort.each do |dep_name|
|
|
99
|
-
dep_pkg = @workspace.find_package(dep_name)
|
|
100
|
-
digest.update(content_hash(dep_pkg))
|
|
100
|
+
pkg = name == package.name ? package : @workspace.find_package(name)
|
|
101
|
+
computed = compute_single_package_hash(pkg)
|
|
102
|
+
@content_hash_mutex.synchronize { @content_hashes[pkg.name] ||= computed }
|
|
101
103
|
end
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
@content_hash_mutex.synchronize do
|
|
105
|
-
@content_hashes[package.name] = computed
|
|
106
|
-
end
|
|
105
|
+
read_memoised_hash(package.name)
|
|
107
106
|
end
|
|
108
107
|
|
|
109
108
|
# Preload cache declarations for multiple packages in parallel.
|
|
@@ -150,6 +149,39 @@ module Rwm
|
|
|
150
149
|
|
|
151
150
|
private
|
|
152
151
|
|
|
152
|
+
def read_memoised_hash(name)
|
|
153
|
+
@content_hash_mutex.synchronize { @content_hashes[name] }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def compute_single_package_hash(package)
|
|
157
|
+
digest = Digest::SHA256.new
|
|
158
|
+
digest.update(CACHE_HASH_VERSION)
|
|
159
|
+
|
|
160
|
+
# Hash all source files in the package (sorted for determinism).
|
|
161
|
+
# Stream in 64 KiB chunks so multi-MB files don't load whole into memory.
|
|
162
|
+
source_files(package).each do |file|
|
|
163
|
+
rel_path = file.delete_prefix("#{package.path}/")
|
|
164
|
+
digest.update(rel_path)
|
|
165
|
+
File.open(file, "rb") do |f|
|
|
166
|
+
while (chunk = f.read(64 * 1024))
|
|
167
|
+
digest.update(chunk)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Dep hashes are guaranteed memoised by the topological iteration in #content_hash.
|
|
173
|
+
# A nil here means the graph and the in-memory state disagree — surface it
|
|
174
|
+
# as a typed cache error rather than a generic TypeError from digest.update(nil).
|
|
175
|
+
@graph.dependencies(package.name).sort.each do |dep_name|
|
|
176
|
+
dep_hash = read_memoised_hash(dep_name)
|
|
177
|
+
raise Rwm::CacheError, "Missing memoised hash for dep '#{dep_name}' of '#{package.name}' (stale dep graph?)" if dep_hash.nil?
|
|
178
|
+
|
|
179
|
+
digest.update(dep_hash)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
digest.hexdigest
|
|
183
|
+
end
|
|
184
|
+
|
|
153
185
|
def source_files(package)
|
|
154
186
|
# Tracked files + untracked-but-not-ignored files (null-delimited for safe filenames)
|
|
155
187
|
output, _, status = Open3.capture3(
|
data/lib/rwm/version.rb
CHANGED