ruby_workspace_manager 0.6.1 → 0.6.3
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 +117 -22
- data/bin/rwm +1 -1
- data/lib/ruby_workspace_manager.rb +3 -0
- data/lib/rwm/affected_detector.rb +8 -0
- data/lib/rwm/commands/graph.rb +9 -0
- data/lib/rwm/commands/run.rb +8 -4
- data/lib/rwm/dependency_graph.rb +19 -2
- data/lib/rwm/gemfile.rb +23 -1
- data/lib/rwm/rails.rb +15 -4
- data/lib/rwm/task_cache.rb +8 -2
- data/lib/rwm/task_runner.rb +3 -0
- data/lib/rwm/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abb2b2c2c95382842ff412711726e5180b6b044b09278f74fd41bce2d29ae7e0
|
|
4
|
+
data.tar.gz: 2370c792ceb61d94619822c8b7a6f63277284f178e72d6a3539f782496d64ba1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6746e8ced0a14de677e83de564c7477e89b0f2519a37447dc1a94dbe28acc3cef2b848c1b3bb2604afe24cd399050d677eceb2dbea194a87163c564df1cada4
|
|
7
|
+
data.tar.gz: 076e897c277f1074f5c4eef7ee834e5c1920050569783e2a84bac836aa9bb4c82aef182390294663068a24fd6690c172e6946b38ea2e29f0bc9289803ac47283
|
data/README.md
CHANGED
|
@@ -211,6 +211,13 @@ rwm_lib "auth", require: false
|
|
|
211
211
|
|
|
212
212
|
There is no `rwm_app` helper. Applications are leaf nodes — nothing should depend on them.
|
|
213
213
|
|
|
214
|
+
`rwm_lib` validates that the library directory exists. If you reference a library that hasn't been created yet, you'll get a clear error:
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
rwm_lib 'payments': no library found at libs/payments.
|
|
218
|
+
Libraries must live in libs/. Create one with: rwm new lib payments
|
|
219
|
+
```
|
|
220
|
+
|
|
214
221
|
You can also use raw `gem ... path:` syntax directly. Both work identically for dependency detection.
|
|
215
222
|
|
|
216
223
|
### Transitive resolution
|
|
@@ -303,6 +310,15 @@ rwm run spec --buffered
|
|
|
303
310
|
|
|
304
311
|
When a package fails, its transitive dependents are immediately skipped. Unrelated packages continue running. The exit code is 0 if all packages pass, 1 if any fail.
|
|
305
312
|
|
|
313
|
+
The summary distinguishes between skip reasons:
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
5 package(s): 2 passed, 1 failed, 1 skipped (dep failed), 1 skipped (no task).
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
- **skipped (dep failed)** — a dependency failed, so this package was not attempted
|
|
320
|
+
- **skipped (no task)** — the package's Rakefile doesn't define the requested task
|
|
321
|
+
|
|
306
322
|
## Task caching
|
|
307
323
|
|
|
308
324
|
### Why caching matters
|
|
@@ -528,6 +544,8 @@ rwm affected --base develop
|
|
|
528
544
|
rwm run spec --affected --base develop
|
|
529
545
|
```
|
|
530
546
|
|
|
547
|
+
If the provided `--base` ref doesn't exist, RWM errors immediately instead of silently returning no affected packages.
|
|
548
|
+
|
|
531
549
|
## Bootstrap and daily workflow
|
|
532
550
|
|
|
533
551
|
### What bootstrap does
|
|
@@ -544,6 +562,8 @@ rwm run spec --affected --base develop
|
|
|
544
562
|
|
|
545
563
|
Both `rwm init` and `rwm bootstrap` are idempotent.
|
|
546
564
|
|
|
565
|
+
**Note on parallel installs:** Step 4 runs `bundle install` concurrently across packages. If your packages share a gem installation directory (the default), you may see Bundler log `Waiting for another process to let go of lock`. This is normal — Bundler serializes writes to the shared directory automatically. On large monorepos with many packages, this can slow down bootstrap. If this becomes a bottleneck, consider using `BUNDLE_PATH` per-package or running bootstrap sequentially.
|
|
566
|
+
|
|
547
567
|
### The bootstrap rake task
|
|
548
568
|
|
|
549
569
|
Every scaffolded package includes an empty `bootstrap` task. This is where package-specific setup belongs:
|
|
@@ -610,60 +630,135 @@ Exits 0 on pass, 1 on violation. The pre-push hook runs this automatically.
|
|
|
610
630
|
|
|
611
631
|
## Rails and Zeitwerk
|
|
612
632
|
|
|
613
|
-
|
|
633
|
+
### How workspace libs work in Rails
|
|
634
|
+
|
|
635
|
+
Workspace libs declared via `rwm_lib` are path gems. The standard Rails boot sequence handles them automatically:
|
|
614
636
|
|
|
615
|
-
|
|
637
|
+
1. `config/boot.rb` calls `Bundler.setup` — adds all gem `lib/` directories to `$LOAD_PATH`
|
|
638
|
+
2. `config/application.rb` calls `Bundler.require(*Rails.groups)` — auto-requires every gem, including workspace libs and their transitive deps
|
|
639
|
+
3. `config/environment.rb` calls `Rails.application.initialize!` — Zeitwerk activates for the app's own code
|
|
616
640
|
|
|
617
|
-
|
|
641
|
+
By the time Zeitwerk starts in step 3, workspace libs are already loaded as plain Ruby modules. Zeitwerk never touches them — it only manages directories in `config.autoload_paths`.
|
|
642
|
+
|
|
643
|
+
**No special setup is needed in `application.rb`.** A standard Rails template works:
|
|
618
644
|
|
|
619
645
|
```ruby
|
|
620
646
|
# apps/web/Gemfile
|
|
647
|
+
require "rwm/gemfile"
|
|
648
|
+
|
|
621
649
|
source "https://rubygems.org"
|
|
622
650
|
gemspec
|
|
623
651
|
|
|
624
|
-
require "rwm/gemfile"
|
|
625
|
-
|
|
626
652
|
rwm_lib "auth" # transitive deps resolved automatically
|
|
627
653
|
```
|
|
628
654
|
|
|
629
|
-
|
|
655
|
+
```ruby
|
|
656
|
+
# apps/web/config/application.rb
|
|
657
|
+
require_relative "boot"
|
|
658
|
+
require "rails/all"
|
|
659
|
+
Bundler.require(*Rails.groups)
|
|
660
|
+
|
|
661
|
+
module Web
|
|
662
|
+
class Application < Rails::Application
|
|
663
|
+
config.load_defaults 8.0
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
That's it. `Bundler.require` loads `auth` and all of its transitive workspace dependencies. No manual `Rwm.require_libs`, no ordering tricks.
|
|
669
|
+
|
|
670
|
+
### A note on Zeitwerk
|
|
671
|
+
|
|
672
|
+
> [!IMPORTANT]
|
|
673
|
+
> **Correction (v0.6.2):** Documentation in v0.6.1 and earlier incorrectly stated that Zeitwerk overrides `Kernel#require`. This was wrong. Zeitwerk uses `Module#autoload` and `const_missing` to lazily load files from `config.autoload_paths`. A plain `require "auth"` (from `Bundler.require` or anywhere else) works normally at any point during the boot sequence — Zeitwerk does not intercept it.
|
|
674
|
+
|
|
675
|
+
### The practical lib workflow
|
|
676
|
+
|
|
677
|
+
**Develop inside your Rails app first.** While a feature is in active development, keep the code in your Rails app's `app/` directory where Zeitwerk gives you hot reloading for free. Change a file, refresh the page, see the result.
|
|
678
|
+
|
|
679
|
+
**Extract when stable.** When the code has solidified — the interface is settled, multiple apps could use it, you're not changing it every day — extract it into a workspace lib. This is the natural monorepo rhythm: apps are where you experiment, libs are where you consolidate.
|
|
680
|
+
|
|
681
|
+
At extraction time, choose how the lib is structured.
|
|
682
|
+
|
|
683
|
+
### Traditional structure (the default)
|
|
684
|
+
|
|
685
|
+
This is what `rwm new lib` scaffolds. The lib's entry point loads all sub-files eagerly with `require_relative`:
|
|
630
686
|
|
|
631
687
|
```ruby
|
|
632
|
-
#
|
|
633
|
-
|
|
688
|
+
# libs/auth/lib/auth.rb
|
|
689
|
+
require_relative "auth/token"
|
|
690
|
+
require_relative "auth/user"
|
|
691
|
+
|
|
692
|
+
module Auth
|
|
693
|
+
VERSION = "0.1.0"
|
|
694
|
+
end
|
|
634
695
|
```
|
|
635
696
|
|
|
636
|
-
**
|
|
697
|
+
**Pros:** Works everywhere — Rails, non-Rails, any Ruby app. Simple. Standard gem structure.
|
|
698
|
+
|
|
699
|
+
**Cons:** No hot reloading in Rails development. After changing a lib file, you restart the server. This is fine for stable extracted code — you're not changing it often.
|
|
700
|
+
|
|
701
|
+
This is the right choice for most workspace libs.
|
|
702
|
+
|
|
703
|
+
### Zeitwerk-compatible structure (opt-in)
|
|
704
|
+
|
|
705
|
+
Choose this when you're still actively iterating on a lib **and** multiple Rails apps consume it. The lib follows Zeitwerk naming conventions — one constant per file, no `require_relative`:
|
|
637
706
|
|
|
638
707
|
```ruby
|
|
639
|
-
#
|
|
640
|
-
|
|
708
|
+
# libs/auth/lib/auth.rb
|
|
709
|
+
module Auth
|
|
710
|
+
end
|
|
641
711
|
|
|
642
|
-
|
|
643
|
-
|
|
712
|
+
# libs/auth/lib/auth/token.rb — defines Auth::Token
|
|
713
|
+
# libs/auth/lib/auth/user.rb — defines Auth::User
|
|
714
|
+
# Zeitwerk auto-discovers these. No require lines needed.
|
|
715
|
+
```
|
|
644
716
|
|
|
645
|
-
require
|
|
646
|
-
require "action_controller/railtie"
|
|
717
|
+
Each consuming Rails app opts in by adding the lib to its autoload paths and telling Bundler not to auto-require it:
|
|
647
718
|
|
|
719
|
+
```ruby
|
|
720
|
+
# apps/web/Gemfile
|
|
721
|
+
rwm_lib "auth", require: false # Bundler won't auto-require
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
```ruby
|
|
725
|
+
# apps/web/config/application.rb
|
|
648
726
|
module Web
|
|
649
727
|
class Application < Rails::Application
|
|
650
|
-
config.
|
|
728
|
+
config.autoload_paths << Rwm.lib_path("auth")
|
|
729
|
+
config.eager_load_paths << Rwm.lib_path("auth")
|
|
651
730
|
end
|
|
652
731
|
end
|
|
653
732
|
```
|
|
654
733
|
|
|
655
|
-
|
|
734
|
+
Now Zeitwerk manages `auth` — lazy loading in development (with hot reloading), eager loading in production. Changes to lib files are picked up on the next request without restarting the server.
|
|
656
735
|
|
|
657
|
-
|
|
736
|
+
**Trade-offs:**
|
|
658
737
|
|
|
659
|
-
|
|
738
|
+
- All consumer apps must add the lib to their autoload paths — this is a per-app decision
|
|
739
|
+
- The lib cannot use `require_relative` for its own files (Zeitwerk must control loading)
|
|
740
|
+
- Non-Rails consumers need a different loading strategy (e.g., `Zeitwerk::Loader.for_gem` or a `Dir.glob` require)
|
|
660
741
|
|
|
661
742
|
### What doesn't work
|
|
662
743
|
|
|
663
|
-
|
|
664
|
-
|
|
744
|
+
**Mixing `Bundler.require` and `autoload_paths` for the same lib.** If `Bundler.require` loads a lib (the default) and you also add it to `config.autoload_paths`, the lib's constants are loaded twice — once eagerly by Bundler, once lazily by Zeitwerk. Reloading breaks because Zeitwerk didn't control the initial load. Pick one or the other per lib.
|
|
745
|
+
|
|
746
|
+
**Using `require_relative` inside a Zeitwerk-managed lib.** Initial loading works fine — Zeitwerk tolerates other loading mechanisms. But after a Zeitwerk reload cycle (in development), files loaded by `require_relative` are still in `$LOADED_FEATURES`. Ruby's `require_relative` sees them as already loaded and skips them. The constants were removed by Zeitwerk's reload but never re-defined. Result: `NameError`.
|
|
747
|
+
|
|
748
|
+
### `Rwm.require_libs` — when you need it
|
|
749
|
+
|
|
750
|
+
For standard Rails apps, `Bundler.require` handles everything. `Rwm.require_libs` exists for edge cases:
|
|
751
|
+
|
|
752
|
+
- Non-standard Rails setups that don't call `Bundler.require`
|
|
753
|
+
- Non-Rails apps that want to load all workspace libs in one call
|
|
754
|
+
- Explicit control over when workspace libs are loaded
|
|
755
|
+
|
|
756
|
+
```ruby
|
|
757
|
+
require "rwm/rails"
|
|
758
|
+
Rwm.require_libs # requires all libs resolved by rwm_lib, idempotent
|
|
759
|
+
```
|
|
665
760
|
|
|
666
|
-
Non-Rails apps don't
|
|
761
|
+
Non-Rails apps don't need any of this — `require` workspace libs from your Gemfile anywhere in your code, as with any gem.
|
|
667
762
|
|
|
668
763
|
## VSCode integration
|
|
669
764
|
|
data/bin/rwm
CHANGED
|
@@ -24,6 +24,7 @@ module Rwm
|
|
|
24
24
|
@graph = graph
|
|
25
25
|
@committed_only = committed_only
|
|
26
26
|
@base_branch = base_branch || detect_base_branch
|
|
27
|
+
validate_base_branch! if base_branch
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
# Returns packages directly changed + their transitive dependents
|
|
@@ -57,6 +58,13 @@ module Rwm
|
|
|
57
58
|
|
|
58
59
|
private
|
|
59
60
|
|
|
61
|
+
def validate_base_branch!
|
|
62
|
+
_, _, status = Open3.capture3("git", "-C", workspace.root, "rev-parse", "--verify", "#{@base_branch}^{commit}")
|
|
63
|
+
return if status.success?
|
|
64
|
+
|
|
65
|
+
raise Rwm::Error, "Base ref '#{@base_branch}' does not exist. Check the branch name or pass a valid --base ref."
|
|
66
|
+
end
|
|
67
|
+
|
|
60
68
|
def detect_base_branch
|
|
61
69
|
# Try to read the remote's default branch
|
|
62
70
|
ref, _, status = Open3.capture3("git", "-C", workspace.root, "symbolic-ref", "refs/remotes/origin/HEAD")
|
data/lib/rwm/commands/graph.rb
CHANGED
|
@@ -23,6 +23,15 @@ module Rwm
|
|
|
23
23
|
puts graph.to_dot
|
|
24
24
|
when :mermaid
|
|
25
25
|
puts graph.to_mermaid
|
|
26
|
+
else
|
|
27
|
+
# Show a brief package listing when no format is requested
|
|
28
|
+
unless graph.packages.empty?
|
|
29
|
+
graph.packages.each_value do |pkg|
|
|
30
|
+
deps = graph.edges[pkg.name] || []
|
|
31
|
+
dep_str = deps.empty? ? "" : " → #{deps.join(", ")}"
|
|
32
|
+
puts " #{pkg.type == "lib" ? "lib" : "app"}/#{pkg.name}#{dep_str}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
26
35
|
end
|
|
27
36
|
|
|
28
37
|
0
|
data/lib/rwm/commands/run.rb
CHANGED
|
@@ -100,21 +100,25 @@ module Rwm
|
|
|
100
100
|
|
|
101
101
|
passed = runner.results.count(&:passed?)
|
|
102
102
|
failed_results = runner.results.select { |r| r.failed? || r.errored? }
|
|
103
|
-
|
|
103
|
+
no_task = runner.results.count(&:skipped?)
|
|
104
|
+
dep_failed = runner.results.count(&:dep_skipped?)
|
|
104
105
|
|
|
105
106
|
total = runner.results.size
|
|
106
107
|
parts = []
|
|
107
108
|
parts << "#{passed} passed" unless passed.zero?
|
|
108
109
|
parts << "#{failed_results.size} failed" unless failed_results.empty?
|
|
109
|
-
parts << "#{
|
|
110
|
+
parts << "#{dep_failed} skipped (dep failed)" unless dep_failed.zero?
|
|
111
|
+
parts << "#{no_task} skipped (no task)" unless no_task.zero?
|
|
110
112
|
|
|
111
113
|
puts
|
|
112
114
|
puts "#{total} package(s): #{parts.join(", ")}."
|
|
113
115
|
|
|
114
116
|
passed_results = runner.results.select(&:passed?)
|
|
115
|
-
|
|
117
|
+
no_task_results = runner.results.select(&:skipped?)
|
|
118
|
+
dep_skipped_results = runner.results.select(&:dep_skipped?)
|
|
116
119
|
Rwm.debug("passed: #{passed_results.map(&:package_name).join(", ")}") unless passed_results.empty?
|
|
117
|
-
Rwm.debug("skipped (no matching task): #{
|
|
120
|
+
Rwm.debug("skipped (no matching task): #{no_task_results.map(&:package_name).join(", ")}") unless no_task_results.empty?
|
|
121
|
+
Rwm.debug("skipped (dep failed): #{dep_skipped_results.map(&:package_name).join(", ")}") unless dep_skipped_results.empty?
|
|
118
122
|
|
|
119
123
|
if failed_results.empty?
|
|
120
124
|
0
|
data/lib/rwm/dependency_graph.rb
CHANGED
|
@@ -103,13 +103,30 @@ module Rwm
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
Rwm.debug("graph: loading from cache at #{path}")
|
|
106
|
-
|
|
106
|
+
begin
|
|
107
|
+
data = JSON.parse(read_locked(path))
|
|
108
|
+
rescue Errno::ENOENT
|
|
109
|
+
Rwm.debug("graph: cache file disappeared, rebuilding")
|
|
110
|
+
return build_and_save(workspace)
|
|
111
|
+
rescue JSON::ParserError
|
|
112
|
+
Rwm.debug("graph: cache file contains invalid JSON, rebuilding")
|
|
113
|
+
return build_and_save(workspace)
|
|
114
|
+
end
|
|
115
|
+
|
|
107
116
|
graph = new
|
|
108
117
|
|
|
109
118
|
workspace.packages.each { |pkg| graph.add_package(pkg) }
|
|
110
119
|
|
|
111
120
|
data["edges"]&.each do |name, deps|
|
|
112
|
-
|
|
121
|
+
next unless graph.packages.key?(name)
|
|
122
|
+
|
|
123
|
+
deps.each do |dep|
|
|
124
|
+
if graph.packages.key?(dep)
|
|
125
|
+
graph.add_edge(name, dep)
|
|
126
|
+
else
|
|
127
|
+
Rwm.debug("graph: skipping stale edge #{name} -> #{dep} (package removed)")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
113
130
|
end
|
|
114
131
|
|
|
115
132
|
graph
|
data/lib/rwm/gemfile.rb
CHANGED
|
@@ -17,17 +17,33 @@ require "set"
|
|
|
17
17
|
|
|
18
18
|
module Rwm
|
|
19
19
|
@resolved_libs = Set.new
|
|
20
|
+
@workspace_root = nil
|
|
20
21
|
|
|
21
22
|
def self.resolved_libs
|
|
22
23
|
@resolved_libs
|
|
23
24
|
end
|
|
24
25
|
|
|
26
|
+
def self.workspace_root
|
|
27
|
+
@workspace_root
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.workspace_root=(path)
|
|
31
|
+
@workspace_root = path
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.lib_path(name)
|
|
35
|
+
raise "rwm: workspace root not set (was rwm_lib used in the Gemfile?)" unless @workspace_root
|
|
36
|
+
|
|
37
|
+
File.join(@workspace_root, "libs", name.to_s, "lib")
|
|
38
|
+
end
|
|
39
|
+
|
|
25
40
|
module GemfileDsl
|
|
26
41
|
def rwm_workspace_root
|
|
27
42
|
@rwm_workspace_root ||= begin
|
|
28
43
|
out, _, status = Open3.capture3("git", "rev-parse", "--show-toplevel")
|
|
29
44
|
root = status.success? ? out.strip : ""
|
|
30
45
|
raise "rwm: not inside a git repository" if root.empty?
|
|
46
|
+
Rwm.workspace_root ||= root
|
|
31
47
|
root
|
|
32
48
|
end
|
|
33
49
|
end
|
|
@@ -37,10 +53,16 @@ module Rwm
|
|
|
37
53
|
@rwm_resolved ||= Set.new
|
|
38
54
|
return if @rwm_resolved.include?(name)
|
|
39
55
|
|
|
56
|
+
path = File.join(rwm_workspace_root, "libs", name)
|
|
57
|
+
|
|
58
|
+
unless File.directory?(path)
|
|
59
|
+
raise "rwm_lib '#{name}': no library found at libs/#{name}. " \
|
|
60
|
+
"Libraries must live in libs/. Create one with: rwm new lib #{name}"
|
|
61
|
+
end
|
|
62
|
+
|
|
40
63
|
@rwm_resolved.add(name)
|
|
41
64
|
Rwm.resolved_libs.add(name) unless @rwm_scanning
|
|
42
65
|
|
|
43
|
-
path = File.join(rwm_workspace_root, "libs", name)
|
|
44
66
|
gem(name, **opts, path: path)
|
|
45
67
|
|
|
46
68
|
# Resolve transitive workspace deps from the target lib's Gemfile
|
data/lib/rwm/rails.rb
CHANGED
|
@@ -2,17 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
# Rails integration for RWM workspaces.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
5
|
+
# For standard Rails apps, Bundler.require handles workspace libs
|
|
6
|
+
# automatically — no manual require_libs call is needed.
|
|
7
|
+
#
|
|
8
|
+
# This file is for non-standard setups or explicit control:
|
|
6
9
|
#
|
|
7
|
-
# require_relative "boot"
|
|
8
10
|
# require "rwm/rails"
|
|
9
11
|
# Rwm.require_libs
|
|
10
|
-
# require "rails"
|
|
11
12
|
|
|
12
13
|
require "rwm/gemfile"
|
|
13
14
|
|
|
14
15
|
module Rwm
|
|
16
|
+
@libs_required = false
|
|
17
|
+
|
|
18
|
+
def self.libs_required?
|
|
19
|
+
@libs_required
|
|
20
|
+
end
|
|
21
|
+
|
|
15
22
|
def self.require_libs
|
|
16
|
-
|
|
23
|
+
return if @libs_required
|
|
24
|
+
|
|
25
|
+
resolved_libs.each { |name| require name }
|
|
26
|
+
@libs_required = true
|
|
27
|
+
debug("required #{resolved_libs.size} workspace lib(s): #{resolved_libs.to_a.sort.join(', ')}")
|
|
17
28
|
end
|
|
18
29
|
end
|
data/lib/rwm/task_cache.rb
CHANGED
|
@@ -24,6 +24,7 @@ module Rwm
|
|
|
24
24
|
@graph = graph
|
|
25
25
|
@cache_dir = File.join(workspace.root, ".rwm", "cache")
|
|
26
26
|
@content_hashes = {}
|
|
27
|
+
@content_hash_mutex = Mutex.new
|
|
27
28
|
@cache_declarations = {}
|
|
28
29
|
@declarations_mutex = Mutex.new
|
|
29
30
|
end
|
|
@@ -78,7 +79,9 @@ module Rwm
|
|
|
78
79
|
|
|
79
80
|
# Compute a content hash for a package: SHA256 of all source files + dependency hashes
|
|
80
81
|
def content_hash(package)
|
|
81
|
-
|
|
82
|
+
@content_hash_mutex.synchronize do
|
|
83
|
+
return @content_hashes[package.name] if @content_hashes.key?(package.name)
|
|
84
|
+
end
|
|
82
85
|
|
|
83
86
|
digest = Digest::SHA256.new
|
|
84
87
|
|
|
@@ -97,7 +100,10 @@ module Rwm
|
|
|
97
100
|
digest.update(content_hash(dep_pkg))
|
|
98
101
|
end
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
computed = digest.hexdigest
|
|
104
|
+
@content_hash_mutex.synchronize do
|
|
105
|
+
@content_hashes[package.name] = computed
|
|
106
|
+
end
|
|
101
107
|
end
|
|
102
108
|
|
|
103
109
|
# Preload cache declarations for multiple packages in parallel.
|
data/lib/rwm/task_runner.rb
CHANGED
|
@@ -73,6 +73,9 @@ module Rwm
|
|
|
73
73
|
running[pkg.name] = Thread.new do
|
|
74
74
|
begin
|
|
75
75
|
result = run_single(pkg, &command_proc)
|
|
76
|
+
rescue IOError
|
|
77
|
+
# Thread killed during I/O (Ctrl+C) — suppress noise
|
|
78
|
+
next
|
|
76
79
|
rescue => e
|
|
77
80
|
result = Result.new(
|
|
78
81
|
package_name: pkg.name, task: "error",
|
data/lib/rwm/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_workspace_manager
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Siddharth Bhatt
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: tsort
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
description: Convention-over-configuration monorepo tool for Ruby. Manages dependency
|
|
13
27
|
graphs, runs tasks in parallel, detects affected packages, and enforces structural
|
|
14
28
|
conventions.
|
|
@@ -22,6 +36,7 @@ files:
|
|
|
22
36
|
- bin/rwm
|
|
23
37
|
- completions/rwm.bash
|
|
24
38
|
- completions/rwm.zsh
|
|
39
|
+
- lib/ruby_workspace_manager.rb
|
|
25
40
|
- lib/rwm.rb
|
|
26
41
|
- lib/rwm/affected_detector.rb
|
|
27
42
|
- lib/rwm/cli.rb
|