gem-repair 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/README.md +98 -36
- data/lib/rubygems/commands/repair_command.rb +3 -2
- 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: eb2259f7f61d7d6f437104759221a3b35fffc953aaaf7c625d39d6b50b102e75
|
|
4
|
+
data.tar.gz: ba502c3f411d10830de34adfa89be29ecbdceac2fbb091bc0e41a393a5bb004d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92ad9f8df853acf84d8614dad654cfb1f4379b6f9eb4537b5d1e136f224909ff7342aed0cc811526fc609c67e810872813a72cd1bd640062cfec45df677bff96
|
|
7
|
+
data.tar.gz: 4e03a34df2a763f8d25d10b4490e1a29d49864a40deb4891e7a5c685299d7182e588a71c80aac2201a8b95d43bee816df6f2663aacfb26a99ba22c7c1985b779
|
data/README.md
CHANGED
|
@@ -1,43 +1,103 @@
|
|
|
1
1
|
# gem-repair
|
|
2
2
|
|
|
3
|
-
A RubyGems plugin
|
|
3
|
+
A RubyGems plugin for the broken C extensions you get when several rubies share one `GEM_HOME`.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## The problem
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Version managers such as mise and rbenv let every ruby point at the same `GEM_HOME`, which saves installing the same gems once per ruby. C extensions do not survive that, because `gem install` compiles them only for the ruby that runs it.
|
|
8
|
+
|
|
9
|
+
Install `bcrypt` under ruby 3.3 into a shared `GEM_HOME`:
|
|
10
|
+
|
|
11
|
+
```console
|
|
12
|
+
$ GEM_HOME=/tmp/shared mise x ruby@3.3 -- gem install bcrypt
|
|
13
|
+
Building native extensions. This could take a while...
|
|
14
|
+
Successfully installed bcrypt-3.1.22
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
RubyGems writes the compiled extension to two places:
|
|
8
18
|
|
|
9
|
-
```sh
|
|
10
|
-
gem build gem-repair.gemspec
|
|
11
|
-
gem install ./gem-repair-0.1.0.gem
|
|
12
19
|
```
|
|
20
|
+
/tmp/shared/extensions/arm64-darwin-27/3.3.0/bcrypt-3.1.22/bcrypt_ext.bundle
|
|
21
|
+
/tmp/shared/gems/bcrypt-3.1.22/lib/bcrypt_ext.bundle
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The first is per-ABI and belongs to ruby 3.3 alone. The second is a copy inside the gem's own `lib`, which every ruby shares. Each one breaks the other rubies in a different way.
|
|
25
|
+
|
|
26
|
+
### The gem is ignored
|
|
13
27
|
|
|
14
|
-
|
|
28
|
+
Ruby 3.4 looks for `extensions/arm64-darwin-27/3.4.0/`, does not find it, and drops the gem off the load path:
|
|
15
29
|
|
|
16
|
-
```
|
|
17
|
-
|
|
30
|
+
```console
|
|
31
|
+
$ GEM_HOME=/tmp/shared mise x ruby@3.4 -- ruby -e 'require "bcrypt"'
|
|
32
|
+
Ignoring bcrypt-3.1.22 because its extensions are not built. Try: gem pristine bcrypt --version 3.1.22
|
|
33
|
+
-e:1:in 'Kernel#require': cannot load such file -- bcrypt (LoadError)
|
|
18
34
|
```
|
|
19
35
|
|
|
20
|
-
|
|
36
|
+
### The copy in lib shadows the right one
|
|
37
|
+
|
|
38
|
+
Build the extension for 3.4 as well and the gem is no longer ignored. It still fails, because `lib` comes before `extensions/` on the load path, so 3.4 loads the object file linked against ruby 3.3:
|
|
39
|
+
|
|
40
|
+
```console
|
|
41
|
+
$ GEM_HOME=/tmp/shared mise x ruby@3.4 -- ruby -e 'require "bcrypt"'
|
|
42
|
+
-e:1:in 'Kernel#require': linked to incompatible /opt/rubies/3.3/lib/libruby.3.3.dylib - /tmp/shared/gems/bcrypt-3.1.22/lib/bcrypt_ext.bundle (LoadError)
|
|
43
|
+
```
|
|
21
44
|
|
|
22
|
-
|
|
45
|
+
Running `gem pristine --all` under each ruby in turn does not settle this. The last ruby to run it wins the copy in `lib` and breaks the others, and the next `gem install` starts the cycle again.
|
|
46
|
+
|
|
47
|
+
## What this plugin does
|
|
48
|
+
|
|
49
|
+
It covers the problem from three directions.
|
|
50
|
+
|
|
51
|
+
1. After every install it deletes the extension copy from the gem's `lib`, so no ruby can load another ruby's object file.
|
|
52
|
+
2. After every install it builds the extensions of the gem just installed for the other rubies sharing the `GEM_HOME`, so switching rubies does not leave the gem ignored.
|
|
53
|
+
3. `gem repair` does both across a `GEM_HOME` that is already broken.
|
|
54
|
+
|
|
55
|
+
With the plugin installed, one `gem install` under 3.3 leaves 3.4 working:
|
|
56
|
+
|
|
57
|
+
```console
|
|
58
|
+
$ GEM_HOME=/tmp/shared GEM_REPAIR_RUBIES=3.4 mise x ruby@3.3 -- gem install bcrypt
|
|
59
|
+
Building native extensions. This could take a while...
|
|
60
|
+
Removed /tmp/shared/gems/bcrypt-3.1.22/lib/bcrypt_ext.bundle
|
|
61
|
+
Built bcrypt-3.1.22 extensions for 3.4
|
|
62
|
+
Successfully installed bcrypt-3.1.22
|
|
63
|
+
|
|
64
|
+
$ GEM_HOME=/tmp/shared mise x ruby@3.4 -- ruby -e 'require "bcrypt"; puts BCrypt::Password.create("x")'
|
|
65
|
+
$2a$12$ko9GRH9PfrJSkF4yD4xXA.BZILRlBpCyOVTnB2XvkYYo5r/cT5FOi
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
23
69
|
|
|
24
70
|
```sh
|
|
25
|
-
gem repair
|
|
71
|
+
gem install gem-repair
|
|
26
72
|
```
|
|
27
73
|
|
|
28
|
-
|
|
74
|
+
Install it into the shared `GEM_HOME`, not into a per-ruby one. RubyGems loads plugins from `GEM_PATH`, so one copy there is what makes every ruby run the hook.
|
|
75
|
+
|
|
76
|
+
Rubies below 2.7 load the plugin too. They get the `gem repair` command but not the install hook.
|
|
77
|
+
|
|
78
|
+
## Repairing a GEM_HOME you already broke
|
|
29
79
|
|
|
30
80
|
```sh
|
|
31
|
-
gem repair
|
|
32
|
-
gem repair --jobs 2 # Use 2 threads
|
|
81
|
+
gem repair
|
|
33
82
|
```
|
|
34
83
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
84
|
+
It sweeps the extension copies out of `lib`, finds every gem whose extensions are missing for the running ruby, and reinstalls them. Reinstalling runs the install hooks, so the fan-out to the other rubies happens here as well.
|
|
85
|
+
|
|
86
|
+
```console
|
|
87
|
+
$ gem repair -j 2
|
|
88
|
+
Searching for gems with missing extensions...
|
|
89
|
+
Removed /tmp/shared/gems/bcrypt-3.1.22/lib/bcrypt_ext.bundle
|
|
90
|
+
Removed /tmp/shared/gems/msgpack-1.8.4/lib/msgpack/msgpack.bundle
|
|
91
|
+
Found 2 gem(s) to repair: msgpack-1.8.4, bcrypt-3.1.22
|
|
92
|
+
Repairing gems using 2 parallel threads...
|
|
93
|
+
Repairing msgpack-1.8.4...
|
|
94
|
+
Repairing bcrypt-3.1.22...
|
|
95
|
+
Successfully repaired bcrypt-3.1.22 to /tmp/shared
|
|
96
|
+
Successfully repaired msgpack-1.8.4 to /tmp/shared
|
|
97
|
+
Gem repair process complete.
|
|
98
|
+
```
|
|
39
99
|
|
|
40
|
-
|
|
100
|
+
Start with `-n` if you want to see the plan first.
|
|
41
101
|
|
|
42
102
|
| Option | Effect |
|
|
43
103
|
| --- | --- |
|
|
@@ -46,23 +106,13 @@ Options:
|
|
|
46
106
|
| `--aggressive-sweep` | Also remove the `test`, `spec`, `features` and `tmp` directories of every gem |
|
|
47
107
|
| `-n`, `--dry-run` | Show what would be removed, repaired or uninstalled without changing anything |
|
|
48
108
|
|
|
49
|
-
##
|
|
50
|
-
|
|
51
|
-
RubyGems copies each built extension into the gem's `lib` directory as well as the per-ruby `extensions/<platform>/<abi>/` directory. RubyGems 3.6 added the `install_extension_in_lib` gemrc setting to skip that copy, but it stays on by default. `lib` comes first on the load path, so every other ruby sharing the `GEM_HOME` loads the copy built for the installing ruby and fails with a `LoadError`. The plugin removes those copies right after each install and on every `gem repair`. Copies under versioned directories such as `lib/3.3/`, which precompiled gems use to ship one extension per ABI, are left alone.
|
|
52
|
-
|
|
53
|
-
### Migrating from gem-sweep
|
|
54
|
-
|
|
55
|
-
This replaces the `gem sweep` command and the install hook of the gem-sweep plugin. Uninstall it so the two hooks do not run side by side:
|
|
56
|
-
|
|
57
|
-
```sh
|
|
58
|
-
gem uninstall gem-sweep
|
|
59
|
-
```
|
|
109
|
+
## How the sweep picks its targets
|
|
60
110
|
|
|
61
|
-
|
|
111
|
+
RubyGems 3.6 added the `install_extension_in_lib` gemrc setting to skip the copy into `lib`, but it is still on by default, and gems installed by an older RubyGems already have the copy. So the sweep cannot be retired yet.
|
|
62
112
|
|
|
63
|
-
|
|
113
|
+
Only files that also exist under `extensions/` are removed, which is how a copy is told apart from an object file the gem shipped itself. Copies under versioned directories such as `lib/3.3/`, which precompiled gems use to carry one extension per ABI, are left alone.
|
|
64
114
|
|
|
65
|
-
|
|
115
|
+
## How the fan-out finds the other rubies
|
|
66
116
|
|
|
67
117
|
Rubies are looked up under the install roots of mise and rbenv:
|
|
68
118
|
|
|
@@ -71,7 +121,19 @@ Rubies are looked up under the install roots of mise and rbenv:
|
|
|
71
121
|
| `MISE_DATA_DIR` | `$XDG_DATA_HOME/mise` | `installs/ruby/*` |
|
|
72
122
|
| `RBENV_ROOT` | `~/.rbenv` | `versions/*` |
|
|
73
123
|
|
|
74
|
-
`GEM_REPAIR_RUBIES` narrows the targets to a comma-separated list of version names, for example `GEM_REPAIR_RUBIES=3.3-dev,3.4-dev`. Set it to an empty string to turn the fan-out off.
|
|
124
|
+
`GEM_REPAIR_RUBIES` narrows the targets to a comma-separated list of version names, for example `GEM_REPAIR_RUBIES=3.3-dev,3.4-dev`. Set it to an empty string to turn the fan-out off. That is worth doing if you keep dozens of rubies installed but only use a few.
|
|
125
|
+
|
|
126
|
+
JRuby, TruffleRuby, mruby and Rubinius installs are always skipped, as is a ruby whose version the gem does not support. The fan-out does not run inside `bundle install`, though the sweep still does. It only runs for a `GEM_HOME` outside the per-ABI directories the running ruby keeps to itself, `Gem.default_dir` and `Gem.user_dir`, since nothing else reads those.
|
|
127
|
+
|
|
128
|
+
## Migrating from gem-sweep
|
|
129
|
+
|
|
130
|
+
This replaces the `gem sweep` command and the install hook of the gem-sweep plugin. Uninstall it so the two hooks do not run side by side:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
gem uninstall gem-sweep
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`gem sweep --missing-extensions` becomes `gem repair --prune`, `gem sweep --aggressive` becomes `gem repair --aggressive-sweep`, and `gem sweep -n` becomes `gem repair -n`.
|
|
75
137
|
|
|
76
138
|
## Development
|
|
77
139
|
|
|
@@ -81,7 +143,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
81
143
|
|
|
82
144
|
## Contributing
|
|
83
145
|
|
|
84
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/hsbt/gem-
|
|
146
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/hsbt/gem-repair.
|
|
85
147
|
|
|
86
148
|
## License
|
|
87
149
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'zlib'
|
|
2
2
|
require 'rubygems/command'
|
|
3
3
|
require 'rubygems/installer'
|
|
4
|
+
require 'rubygems/uninstaller'
|
|
4
5
|
# see rubygems_plugin.rb
|
|
5
6
|
require_relative '../../gem_repair/sweep' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7")
|
|
6
7
|
|
|
@@ -167,8 +168,8 @@ Use -n to only show what would be done.
|
|
|
167
168
|
end
|
|
168
169
|
|
|
169
170
|
def prune_gem(spec)
|
|
170
|
-
|
|
171
|
-
Gem::Uninstaller.new(spec.name, version: spec.version, install_dir: spec.base_dir, executables: true).uninstall_gem(spec)
|
|
171
|
+
# force skips the dependent-gem prompt, which RubyGems answers no to off a tty.
|
|
172
|
+
Gem::Uninstaller.new(spec.name, version: spec.version, install_dir: spec.base_dir, executables: true, force: true).uninstall_gem(spec)
|
|
172
173
|
rescue Gem::Exception => e
|
|
173
174
|
alert_error "Failed to uninstall #{spec.full_name}: #{e.message}"
|
|
174
175
|
end
|