ruby_workspace_manager 0.5.0 → 0.6.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/README.md +730 -18
- data/lib/rwm/affected_detector.rb +37 -1
- data/lib/rwm/cli.rb +7 -0
- data/lib/rwm/commands/affected.rb +1 -1
- data/lib/rwm/commands/init.rb +10 -1
- data/lib/rwm/commands/new.rb +88 -27
- data/lib/rwm/commands/run.rb +15 -13
- data/lib/rwm/dependency_graph.rb +23 -5
- data/lib/rwm/gemfile.rb +2 -1
- data/lib/rwm/task_cache.rb +36 -7
- data/lib/rwm/task_runner.rb +48 -28
- data/lib/rwm/version.rb +1 -1
- metadata +1 -1
|
@@ -4,6 +4,19 @@ require "open3"
|
|
|
4
4
|
|
|
5
5
|
module Rwm
|
|
6
6
|
class AffectedDetector
|
|
7
|
+
IGNORED_ROOT_PATTERNS = [
|
|
8
|
+
"*.md",
|
|
9
|
+
"LICENSE*",
|
|
10
|
+
"CHANGELOG*",
|
|
11
|
+
".github/**",
|
|
12
|
+
".vscode/**",
|
|
13
|
+
".idea/**",
|
|
14
|
+
"docs/**",
|
|
15
|
+
".rwm/**",
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
IGNORE_FILE = "affected_ignore"
|
|
19
|
+
|
|
7
20
|
attr_reader :workspace, :graph, :base_branch
|
|
8
21
|
|
|
9
22
|
def initialize(workspace, graph, committed_only: false, base_branch: nil)
|
|
@@ -20,7 +33,10 @@ module Rwm
|
|
|
20
33
|
|
|
21
34
|
# If root-level files changed (outside any package), all packages are affected
|
|
22
35
|
root_files = changed_files.reject { |f| file_in_any_package?(f) }
|
|
23
|
-
|
|
36
|
+
significant_root_files = root_files.reject { |f| ignored_root_file?(f) }
|
|
37
|
+
|
|
38
|
+
unless significant_root_files.empty?
|
|
39
|
+
Rwm.debug("affected: significant root files changed: #{significant_root_files.join(', ')}")
|
|
24
40
|
return workspace.packages
|
|
25
41
|
end
|
|
26
42
|
|
|
@@ -111,5 +127,25 @@ module Rwm
|
|
|
111
127
|
file.start_with?("#{rel_path}/")
|
|
112
128
|
end
|
|
113
129
|
end
|
|
130
|
+
|
|
131
|
+
def ignored_root_file?(file)
|
|
132
|
+
ignore_patterns.any? do |pattern|
|
|
133
|
+
File.fnmatch(pattern, file, File::FNM_DOTMATCH)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def ignore_patterns
|
|
138
|
+
patterns = IGNORED_ROOT_PATTERNS.dup
|
|
139
|
+
ignore_file = File.join(workspace.root, ".rwm", IGNORE_FILE)
|
|
140
|
+
if File.exist?(ignore_file)
|
|
141
|
+
File.readlines(ignore_file).each do |line|
|
|
142
|
+
line = line.strip
|
|
143
|
+
next if line.empty? || line.start_with?("#")
|
|
144
|
+
|
|
145
|
+
patterns << line
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
patterns
|
|
149
|
+
end
|
|
114
150
|
end
|
|
115
151
|
end
|
data/lib/rwm/cli.rb
CHANGED
|
@@ -51,9 +51,16 @@ module Rwm
|
|
|
51
51
|
const_name = COMMANDS[command_name]
|
|
52
52
|
command_class = const_name.split("::").reduce(Rwm) { |mod, name| mod.const_get(name) }
|
|
53
53
|
command_class.new(@argv).run
|
|
54
|
+
rescue Interrupt
|
|
55
|
+
$stderr.puts "\nInterrupted."
|
|
56
|
+
130
|
|
54
57
|
rescue Rwm::Error => e
|
|
55
58
|
$stderr.puts "Error: #{e.message}"
|
|
56
59
|
1
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
$stderr.puts "Error: #{e.message}"
|
|
62
|
+
Rwm.debug("#{e.class}: #{e.message}\n#{e.backtrace&.join("\n")}")
|
|
63
|
+
1
|
|
57
64
|
end
|
|
58
65
|
|
|
59
66
|
private
|
data/lib/rwm/commands/init.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
+
require "open3"
|
|
4
5
|
require "optparse"
|
|
5
6
|
|
|
6
7
|
module Rwm
|
|
@@ -11,6 +12,7 @@ module Rwm
|
|
|
11
12
|
|
|
12
13
|
source "https://rubygems.org"
|
|
13
14
|
|
|
15
|
+
gem "rake"
|
|
14
16
|
gem "ruby_workspace_manager"
|
|
15
17
|
GEMFILE
|
|
16
18
|
|
|
@@ -30,7 +32,7 @@ module Rwm
|
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def run
|
|
33
|
-
root =
|
|
35
|
+
root = detect_git_root
|
|
34
36
|
|
|
35
37
|
create_directories(root)
|
|
36
38
|
create_gemfile(root)
|
|
@@ -52,6 +54,13 @@ module Rwm
|
|
|
52
54
|
|
|
53
55
|
private
|
|
54
56
|
|
|
57
|
+
def detect_git_root
|
|
58
|
+
out, _, status = Open3.capture3("git", "rev-parse", "--show-toplevel")
|
|
59
|
+
raise Rwm::Error, "Not inside a git repository. Run `git init` first." unless status.success?
|
|
60
|
+
|
|
61
|
+
out.chomp
|
|
62
|
+
end
|
|
63
|
+
|
|
55
64
|
def parse_options
|
|
56
65
|
OptionParser.new do |opts|
|
|
57
66
|
opts.on("--vscode", "Generate VSCode .code-workspace file") do
|
data/lib/rwm/commands/new.rb
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
+
require "optparse"
|
|
4
5
|
|
|
5
6
|
module Rwm
|
|
6
7
|
module Commands
|
|
7
8
|
class New
|
|
9
|
+
VALID_TEST_FRAMEWORKS = %w[rspec minitest none].freeze
|
|
10
|
+
|
|
8
11
|
def initialize(argv)
|
|
9
12
|
@argv = argv
|
|
13
|
+
@test_framework = "rspec"
|
|
14
|
+
parse_options
|
|
10
15
|
end
|
|
11
16
|
|
|
12
17
|
def run
|
|
@@ -52,35 +57,60 @@ module Rwm
|
|
|
52
57
|
VscodeWorkspace.new(fresh_workspace.root).generate(fresh_workspace.packages)
|
|
53
58
|
end
|
|
54
59
|
|
|
60
|
+
def parse_options
|
|
61
|
+
OptionParser.new do |opts|
|
|
62
|
+
opts.on("--test=FRAMEWORK", VALID_TEST_FRAMEWORKS, "Test framework (#{VALID_TEST_FRAMEWORKS.join(', ')})") do |fw|
|
|
63
|
+
@test_framework = fw
|
|
64
|
+
end
|
|
65
|
+
end.parse!(@argv)
|
|
66
|
+
end
|
|
67
|
+
|
|
55
68
|
def scaffold(pkg_path, name, type)
|
|
56
69
|
source_dir = type == "lib" ? "lib" : "app"
|
|
57
70
|
FileUtils.mkdir_p(File.join(pkg_path, source_dir, name))
|
|
58
|
-
FileUtils.mkdir_p(File.join(pkg_path, "spec"))
|
|
59
71
|
|
|
60
72
|
write_gemfile(pkg_path, name)
|
|
61
73
|
write_gemspec(pkg_path, name, type)
|
|
62
74
|
write_rakefile(pkg_path, name)
|
|
63
75
|
write_entry_file(pkg_path, name, type)
|
|
64
|
-
|
|
76
|
+
|
|
77
|
+
case @test_framework
|
|
78
|
+
when "rspec"
|
|
79
|
+
FileUtils.mkdir_p(File.join(pkg_path, "spec"))
|
|
80
|
+
write_spec_helper(pkg_path)
|
|
81
|
+
when "minitest"
|
|
82
|
+
FileUtils.mkdir_p(File.join(pkg_path, "test"))
|
|
83
|
+
write_test_helper(pkg_path)
|
|
84
|
+
end
|
|
65
85
|
end
|
|
66
86
|
|
|
67
87
|
def write_gemfile(pkg_path, name)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
88
|
+
test_gem_line = case @test_framework
|
|
89
|
+
when "rspec" then ' gem "rspec"'
|
|
90
|
+
when "minitest" then ' gem "minitest"'
|
|
91
|
+
end
|
|
72
92
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
lines = [
|
|
94
|
+
'# frozen_string_literal: true',
|
|
95
|
+
'',
|
|
96
|
+
'source "https://rubygems.org"',
|
|
97
|
+
'',
|
|
98
|
+
'gemspec',
|
|
99
|
+
'',
|
|
100
|
+
'group :development, :test do',
|
|
101
|
+
' gem "rake"',
|
|
102
|
+
]
|
|
103
|
+
lines << test_gem_line if test_gem_line
|
|
104
|
+
lines.concat([
|
|
105
|
+
' gem "ruby_workspace_manager"',
|
|
106
|
+
'end',
|
|
107
|
+
'',
|
|
108
|
+
'require "rwm/gemfile"',
|
|
109
|
+
'# rwm_lib "some_dependency"',
|
|
110
|
+
'',
|
|
111
|
+
])
|
|
80
112
|
|
|
81
|
-
|
|
82
|
-
# rwm_lib "some_dependency"
|
|
83
|
-
GEMFILE
|
|
113
|
+
File.write(File.join(pkg_path, "Gemfile"), lines.join("\n"))
|
|
84
114
|
end
|
|
85
115
|
|
|
86
116
|
def write_gemspec(pkg_path, name, type)
|
|
@@ -106,21 +136,44 @@ module Rwm
|
|
|
106
136
|
end
|
|
107
137
|
|
|
108
138
|
def write_rakefile(pkg_path, name)
|
|
109
|
-
|
|
110
|
-
# frozen_string_literal: true
|
|
139
|
+
lines = [
|
|
140
|
+
'# frozen_string_literal: true',
|
|
141
|
+
'',
|
|
142
|
+
'require "rwm/rake"',
|
|
143
|
+
'',
|
|
144
|
+
]
|
|
111
145
|
|
|
112
|
-
|
|
146
|
+
case @test_framework
|
|
147
|
+
when "rspec"
|
|
148
|
+
lines.concat([
|
|
149
|
+
'cacheable_task :spec do',
|
|
150
|
+
' sh "bundle exec rspec"',
|
|
151
|
+
'end',
|
|
152
|
+
'',
|
|
153
|
+
])
|
|
154
|
+
when "minitest"
|
|
155
|
+
lines.concat([
|
|
156
|
+
'cacheable_task :test do',
|
|
157
|
+
' sh "bundle exec ruby -Ilib:test -e \'Dir.glob(\"test/**/*_test.rb\").each { |f| require_relative f }\'"',
|
|
158
|
+
'end',
|
|
159
|
+
'',
|
|
160
|
+
])
|
|
161
|
+
end
|
|
113
162
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
163
|
+
lines.concat([
|
|
164
|
+
"task :bootstrap do",
|
|
165
|
+
" puts \"Add bootstrap steps for #{name} here.\"",
|
|
166
|
+
"end",
|
|
167
|
+
"",
|
|
168
|
+
])
|
|
117
169
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
170
|
+
if @test_framework != "none"
|
|
171
|
+
task_name = @test_framework == "rspec" ? ":spec" : ":test"
|
|
172
|
+
lines << "task default: #{task_name}"
|
|
173
|
+
lines << ""
|
|
174
|
+
end
|
|
121
175
|
|
|
122
|
-
|
|
123
|
-
RAKEFILE
|
|
176
|
+
File.write(File.join(pkg_path, "Rakefile"), lines.join("\n"))
|
|
124
177
|
end
|
|
125
178
|
|
|
126
179
|
def write_entry_file(pkg_path, name, type)
|
|
@@ -145,6 +198,14 @@ module Rwm
|
|
|
145
198
|
RUBY
|
|
146
199
|
end
|
|
147
200
|
|
|
201
|
+
def write_test_helper(pkg_path)
|
|
202
|
+
File.write(File.join(pkg_path, "test", "test_helper.rb"), <<~RUBY)
|
|
203
|
+
# frozen_string_literal: true
|
|
204
|
+
|
|
205
|
+
require "minitest/autorun"
|
|
206
|
+
RUBY
|
|
207
|
+
end
|
|
208
|
+
|
|
148
209
|
def camelize(name)
|
|
149
210
|
name.split("_").map(&:capitalize).join
|
|
150
211
|
end
|
data/lib/rwm/commands/run.rb
CHANGED
|
@@ -62,6 +62,7 @@ module Rwm
|
|
|
62
62
|
# Auto-detect cacheable tasks unless --no-cache
|
|
63
63
|
cache = TaskCache.new(workspace, graph) unless @no_cache
|
|
64
64
|
if cache
|
|
65
|
+
cache.preload_declarations(runnable)
|
|
65
66
|
cacheable, not_cacheable = runnable.partition { |pkg| cache.cacheable?(pkg, task) }
|
|
66
67
|
cached, uncached = cacheable.partition { |pkg| cache.cached?(pkg, task) }
|
|
67
68
|
cached.each { |pkg| puts "[#{pkg.name}] cached" }
|
|
@@ -90,35 +91,36 @@ module Rwm
|
|
|
90
91
|
# Store cache for successful cacheable packages
|
|
91
92
|
if cache
|
|
92
93
|
runner.results.each do |result|
|
|
93
|
-
next unless result.
|
|
94
|
-
next if result.skipped
|
|
94
|
+
next unless result.passed?
|
|
95
95
|
|
|
96
96
|
pkg = workspace.find_package(result.package_name)
|
|
97
97
|
cache.store(pkg, task) if cache.cacheable?(pkg, task)
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
passed
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
passed = runner.results.count(&:passed?)
|
|
102
|
+
failed_results = runner.results.select { |r| r.failed? || r.errored? }
|
|
103
|
+
skipped = runner.results.count { |r| r.skipped? || r.dep_skipped? }
|
|
104
104
|
|
|
105
105
|
total = runner.results.size
|
|
106
106
|
parts = []
|
|
107
|
-
parts << "#{passed
|
|
108
|
-
parts << "#{
|
|
109
|
-
parts << "#{
|
|
107
|
+
parts << "#{passed} passed" unless passed.zero?
|
|
108
|
+
parts << "#{failed_results.size} failed" unless failed_results.empty?
|
|
109
|
+
parts << "#{skipped} skipped" unless skipped.zero?
|
|
110
110
|
|
|
111
111
|
puts
|
|
112
112
|
puts "#{total} package(s): #{parts.join(", ")}."
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
passed_results = runner.results.select(&:passed?)
|
|
115
|
+
skipped_results = runner.results.select { |r| r.skipped? || r.dep_skipped? }
|
|
116
|
+
Rwm.debug("passed: #{passed_results.map(&:package_name).join(", ")}") unless passed_results.empty?
|
|
117
|
+
Rwm.debug("skipped (no matching task): #{skipped_results.map(&:package_name).join(", ")}") unless skipped_results.empty?
|
|
116
118
|
|
|
117
|
-
if
|
|
119
|
+
if failed_results.empty?
|
|
118
120
|
0
|
|
119
121
|
else
|
|
120
122
|
$stderr.puts "Failed:"
|
|
121
|
-
|
|
123
|
+
failed_results.each { |r| $stderr.puts " - #{r.package_name}" }
|
|
122
124
|
1
|
|
123
125
|
end
|
|
124
126
|
end
|
|
@@ -150,7 +152,7 @@ module Rwm
|
|
|
150
152
|
end
|
|
151
153
|
end
|
|
152
154
|
|
|
153
|
-
parser.
|
|
155
|
+
parser.parse!(@argv)
|
|
154
156
|
end
|
|
155
157
|
end
|
|
156
158
|
end
|
data/lib/rwm/dependency_graph.rb
CHANGED
|
@@ -70,17 +70,17 @@ module Rwm
|
|
|
70
70
|
return [] if @packages.empty?
|
|
71
71
|
|
|
72
72
|
remaining = @packages.keys.dup
|
|
73
|
+
placed = Set.new
|
|
73
74
|
levels = []
|
|
74
75
|
|
|
75
76
|
until remaining.empty?
|
|
76
|
-
# Find packages whose deps are all already placed in earlier levels
|
|
77
|
-
placed = levels.flatten
|
|
78
77
|
level = remaining.select do |name|
|
|
79
78
|
dependencies(name).all? { |dep| placed.include?(dep) }
|
|
80
79
|
end
|
|
81
80
|
|
|
82
81
|
raise CycleError, [["Unable to resolve execution levels — possible cycle"]] if level.empty?
|
|
83
82
|
|
|
83
|
+
level.each { |name| placed.add(name) }
|
|
84
84
|
levels << level.sort
|
|
85
85
|
remaining -= level
|
|
86
86
|
end
|
|
@@ -103,7 +103,7 @@ module Rwm
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
Rwm.debug("graph: loading from cache at #{path}")
|
|
106
|
-
data = JSON.parse(
|
|
106
|
+
data = JSON.parse(read_locked(path))
|
|
107
107
|
graph = new
|
|
108
108
|
|
|
109
109
|
workspace.packages.each { |pkg| graph.add_package(pkg) }
|
|
@@ -126,7 +126,16 @@ module Rwm
|
|
|
126
126
|
packages.any? { |pkg| File.mtime(pkg.gemfile_path) > graph_mtime }
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
def self.read_locked(path)
|
|
130
|
+
File.open(path, "r") do |f|
|
|
131
|
+
f.flock(File::LOCK_SH)
|
|
132
|
+
f.read
|
|
133
|
+
end
|
|
134
|
+
rescue Errno::ENOTSUP
|
|
135
|
+
File.read(path)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private_class_method :stale?, :build_and_save, :read_locked
|
|
130
139
|
|
|
131
140
|
# Build graph from a workspace by parsing all Gemfiles
|
|
132
141
|
def self.build(workspace)
|
|
@@ -158,7 +167,7 @@ module Rwm
|
|
|
158
167
|
@workspace_root = workspace_root
|
|
159
168
|
dir = File.dirname(path)
|
|
160
169
|
FileUtils.mkdir_p(dir)
|
|
161
|
-
|
|
170
|
+
write_locked(path, JSON.pretty_generate(to_json_data) + "\n")
|
|
162
171
|
end
|
|
163
172
|
|
|
164
173
|
def to_dot
|
|
@@ -200,6 +209,15 @@ module Rwm
|
|
|
200
209
|
|
|
201
210
|
private
|
|
202
211
|
|
|
212
|
+
def write_locked(path, content)
|
|
213
|
+
File.open(path, File::CREAT | File::WRONLY | File::TRUNC) do |f|
|
|
214
|
+
f.flock(File::LOCK_EX)
|
|
215
|
+
f.write(content)
|
|
216
|
+
end
|
|
217
|
+
rescue Errno::ENOTSUP
|
|
218
|
+
File.write(path, content)
|
|
219
|
+
end
|
|
220
|
+
|
|
203
221
|
# TSort interface
|
|
204
222
|
def tsort_each_node(&block)
|
|
205
223
|
@packages.each_key(&block)
|
data/lib/rwm/gemfile.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Rwm
|
|
|
38
38
|
return if @rwm_resolved.include?(name)
|
|
39
39
|
|
|
40
40
|
@rwm_resolved.add(name)
|
|
41
|
-
Rwm.resolved_libs.add(name)
|
|
41
|
+
Rwm.resolved_libs.add(name) unless @rwm_scanning
|
|
42
42
|
|
|
43
43
|
path = File.join(rwm_workspace_root, "libs", name)
|
|
44
44
|
gem(name, **opts, path: path)
|
|
@@ -54,6 +54,7 @@ module Rwm
|
|
|
54
54
|
|
|
55
55
|
def scan_transitive_deps(gemfile_path)
|
|
56
56
|
sandbox = Bundler::Dsl.new
|
|
57
|
+
sandbox.instance_variable_set(:@rwm_scanning, true)
|
|
57
58
|
sandbox.eval_gemfile(gemfile_path)
|
|
58
59
|
|
|
59
60
|
libs_prefix = File.join(rwm_workspace_root, "libs") + "/"
|
data/lib/rwm/task_cache.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
|
+
require "etc"
|
|
4
5
|
require "fileutils"
|
|
5
6
|
require "json"
|
|
6
7
|
require "open3"
|
|
@@ -24,6 +25,7 @@ module Rwm
|
|
|
24
25
|
@cache_dir = File.join(workspace.root, ".rwm", "cache")
|
|
25
26
|
@content_hashes = {}
|
|
26
27
|
@cache_declarations = {}
|
|
28
|
+
@declarations_mutex = Mutex.new
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
# Returns true if the task is declared cacheable in the package's Rakefile
|
|
@@ -98,19 +100,46 @@ module Rwm
|
|
|
98
100
|
@content_hashes[package.name] = digest.hexdigest
|
|
99
101
|
end
|
|
100
102
|
|
|
103
|
+
# Preload cache declarations for multiple packages in parallel.
|
|
104
|
+
# Warms the memoization hash so subsequent cacheable?/cached? calls are instant.
|
|
105
|
+
def preload_declarations(packages)
|
|
106
|
+
pending = packages.reject { |pkg| @cache_declarations.key?(pkg.name) }
|
|
107
|
+
return if pending.empty?
|
|
108
|
+
|
|
109
|
+
Rwm.debug("cache declarations: preloading #{pending.size} package(s) in parallel")
|
|
110
|
+
concurrency = [Etc.nprocessors, pending.size].min
|
|
111
|
+
threads = []
|
|
112
|
+
|
|
113
|
+
pending.each_slice((pending.size.to_f / concurrency).ceil) do |batch|
|
|
114
|
+
threads << Thread.new do
|
|
115
|
+
batch.each { |pkg| cache_declarations(pkg) }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
threads.each(&:join)
|
|
120
|
+
end
|
|
121
|
+
|
|
101
122
|
# Discover cacheable task declarations by running `bundle exec rake rwm:cache_config`
|
|
102
123
|
def cache_declarations(package)
|
|
103
|
-
|
|
124
|
+
@declarations_mutex.synchronize do
|
|
125
|
+
return @cache_declarations[package.name] if @cache_declarations.key?(package.name)
|
|
126
|
+
end
|
|
104
127
|
|
|
105
128
|
Rwm.debug("cache declarations: discovering for #{package.name}")
|
|
106
129
|
output, _, status = Open3.capture3("bundle", "exec", "rake", "rwm:cache_config", chdir: package.path)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
result = if status.success? && !output.strip.empty?
|
|
131
|
+
JSON.parse(output.strip)
|
|
132
|
+
else
|
|
133
|
+
{}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@declarations_mutex.synchronize do
|
|
137
|
+
@cache_declarations[package.name] = result
|
|
138
|
+
end
|
|
112
139
|
rescue JSON::ParserError
|
|
113
|
-
@
|
|
140
|
+
@declarations_mutex.synchronize do
|
|
141
|
+
@cache_declarations[package.name] = {}
|
|
142
|
+
end
|
|
114
143
|
end
|
|
115
144
|
|
|
116
145
|
private
|
data/lib/rwm/task_runner.rb
CHANGED
|
@@ -5,9 +5,20 @@ require "etc"
|
|
|
5
5
|
|
|
6
6
|
module Rwm
|
|
7
7
|
class TaskRunner
|
|
8
|
-
Result = Struct.new(:package_name, :task, :
|
|
8
|
+
Result = Struct.new(:package_name, :task, :status, :output, keyword_init: true) do
|
|
9
|
+
def passed? = status == :passed
|
|
10
|
+
def failed? = status == :failed
|
|
11
|
+
def skipped? = status == :skipped
|
|
12
|
+
def dep_skipped? = status == :dep_skipped
|
|
13
|
+
def errored? = status == :errored
|
|
14
|
+
def success? = passed? || skipped?
|
|
15
|
+
end
|
|
9
16
|
|
|
10
|
-
NO_TASK_PATTERN = /
|
|
17
|
+
NO_TASK_PATTERN = /
|
|
18
|
+
don.t\s+know\s+how\s+to\s+build\s+task
|
|
19
|
+
|
|
|
20
|
+
rake\s+--tasks
|
|
21
|
+
/ix
|
|
11
22
|
|
|
12
23
|
attr_reader :results
|
|
13
24
|
|
|
@@ -60,28 +71,38 @@ module Rwm
|
|
|
60
71
|
|
|
61
72
|
pending.delete(pkg)
|
|
62
73
|
running[pkg.name] = Thread.new do
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
begin
|
|
75
|
+
result = run_single(pkg, &command_proc)
|
|
76
|
+
rescue => e
|
|
77
|
+
result = Result.new(
|
|
78
|
+
package_name: pkg.name, task: "error",
|
|
79
|
+
status: :errored, output: "Error: #{e.class}: #{e.message}"
|
|
80
|
+
)
|
|
81
|
+
ensure
|
|
82
|
+
next unless result # thread was killed before completing
|
|
83
|
+
|
|
84
|
+
mutex.synchronize do
|
|
85
|
+
@results << result
|
|
86
|
+
running.delete(pkg.name)
|
|
87
|
+
if result.success?
|
|
88
|
+
completed << pkg.name
|
|
89
|
+
else
|
|
90
|
+
skip_names = @graph.transitive_dependents(pkg.name)
|
|
91
|
+
.select { |n| package_names.include?(n) }
|
|
92
|
+
skip_names.each do |name|
|
|
93
|
+
skip_pkg = pending.find { |p| p.name == name }
|
|
94
|
+
if skip_pkg
|
|
95
|
+
pending.delete(skip_pkg)
|
|
96
|
+
skipped << name
|
|
97
|
+
@results << Result.new(
|
|
98
|
+
package_name: name, task: "skipped",
|
|
99
|
+
status: :dep_skipped, output: "Skipped due to failed dependency: #{pkg.name}"
|
|
100
|
+
)
|
|
101
|
+
end
|
|
81
102
|
end
|
|
82
103
|
end
|
|
104
|
+
condition.broadcast
|
|
83
105
|
end
|
|
84
|
-
condition.broadcast
|
|
85
106
|
end
|
|
86
107
|
end
|
|
87
108
|
end
|
|
@@ -107,11 +128,11 @@ module Rwm
|
|
|
107
128
|
end
|
|
108
129
|
|
|
109
130
|
def success?
|
|
110
|
-
@results.
|
|
131
|
+
@results.none? { |r| r.failed? || r.errored? }
|
|
111
132
|
end
|
|
112
133
|
|
|
113
134
|
def failed_results
|
|
114
|
-
@results.select { |r|
|
|
135
|
+
@results.select { |r| r.failed? || r.errored? }
|
|
115
136
|
end
|
|
116
137
|
|
|
117
138
|
private
|
|
@@ -135,13 +156,12 @@ module Rwm
|
|
|
135
156
|
|
|
136
157
|
# Detect "task not found" and treat as skipped, not failed
|
|
137
158
|
if !status.success? && stderr.match?(NO_TASK_PATTERN)
|
|
138
|
-
Rwm.debug("#{pkg.name}: task not found
|
|
159
|
+
Rwm.debug("#{pkg.name}: task not found (matched: #{stderr.lines.first&.chomp})")
|
|
139
160
|
return Result.new(
|
|
140
161
|
package_name: pkg.name,
|
|
141
162
|
task: cmd.join(" "),
|
|
142
|
-
|
|
143
|
-
output: ""
|
|
144
|
-
skipped: true
|
|
163
|
+
status: :skipped,
|
|
164
|
+
output: ""
|
|
145
165
|
)
|
|
146
166
|
end
|
|
147
167
|
|
|
@@ -154,7 +174,7 @@ module Rwm
|
|
|
154
174
|
Result.new(
|
|
155
175
|
package_name: pkg.name,
|
|
156
176
|
task: cmd.join(" "),
|
|
157
|
-
|
|
177
|
+
status: status.success? ? :passed : :failed,
|
|
158
178
|
output: output
|
|
159
179
|
)
|
|
160
180
|
end
|
data/lib/rwm/version.rb
CHANGED