bundler-source-vault 0.1.5 → 0.2.2

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/gemvault_load_path.rb +163 -0
  3. data/plugins.rb +19 -19
  4. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62096b2c3b6ffd526e3966e2edabb75db044f550297744aea293fb0d2ad44a97
4
- data.tar.gz: 7a701f9b39f34b3d49b097b4e34f11bb6bd74903dd928cc0db7bcf575368bc12
3
+ metadata.gz: 1ef5a7eef66715d1eef861c72b9d99ae3620f507cc67cc3e77ddc9ec388a1e66
4
+ data.tar.gz: cb6bf98bc161d1d69ddfa628787de932c4768a59cbcfe49ebbf01cec5c9ba739
5
5
  SHA512:
6
- metadata.gz: e40163ad4c191c4f4a96180c186ea8fb6d39dc6d4cd3881f934af5b8d9607f1a474ca453c79f6061ece769be33bfcd8db187f1c32f4f9f958992b0d3c9596582
7
- data.tar.gz: b4ab91d8e009e1ea0f385ddb3c38892db673369a2ce3649c709ce1f218f48324f543cb71ed838d98afd6cb4a2f6e5f24113ce5d9d034d9017b0504f674366429
6
+ metadata.gz: 495d5668e91b6508baefbfab28cca129e506da744f1026b93b3eecfe4b8623955fb27c74ee7d54f89b8c21bed561470f44ee65a61ace034abc1048dac7305206
7
+ data.tar.gz: 2c3539ddb5f9f098f39cdbad32747f9aa0d88f013247a464e5a9d8107657cadd621497f24a5f6860f16fe24aea9469815b691923c7f1b16c0af694f5c0b8ad1e
@@ -0,0 +1,163 @@
1
+ module BundlerSourceVault
2
+ # Finds gemvault, and the runtime dependencies it needs, across every gem root
3
+ # that could hold either, and puts their require paths on $LOAD_PATH.
4
+ #
5
+ # Bundler installs a plugin's dependencies alongside the plugin but does not
6
+ # put them on the load path before loading plugins.rb -- see the "Currently
7
+ # not done to avoid conflicts" comment in bundler/plugin.rb -- so the shim has
8
+ # to find gemvault itself.
9
+ #
10
+ # $LOAD_PATH rather than activation, because activation cannot survive here:
11
+ #
12
+ # bundler/runtime.rb clean_load_path runs immediately before specs_for, and
13
+ # resolution is what asks the vault source for specs.
14
+ # bundler/shared_helpers.rb
15
+ # clean_load_path strips a $LOAD_PATH entry only when it
16
+ # appears in loaded_gem_paths.
17
+ # bundler/rubygems_integration.rb
18
+ # loaded_gem_paths is built from Gem.loaded_specs, so
19
+ # activating is precisely what gets these paths removed
20
+ # one line before they are needed; and stub_rubygems
21
+ # sets Gem::Specification.all to the bundle's own specs,
22
+ # reapplying it via Gem.post_reset, so under bundle exec
23
+ # find_by_name cannot see outside the bundle and reset
24
+ # will not restore it.
25
+ #
26
+ # Skipping activation also skips gemvault's dependencies, which is why they
27
+ # are resolved here by hand. One that cannot be found is left out rather than
28
+ # raising: command_kit is needed only by the CLI, and activation would have
29
+ # aborted on it.
30
+ module GemvaultLoadPath
31
+ GEM = "gemvault".freeze
32
+ SHIM = "bundler-source-vault".freeze
33
+ VAULT_SOURCE = "bundler/plugin/vault_source.rb".freeze
34
+
35
+ module_function
36
+
37
+ def apply
38
+ entries.each { |path| $LOAD_PATH.push(path) unless $LOAD_PATH.include?(path) }
39
+ end
40
+
41
+ # Development: loaded from within the gemvault source tree, where the gem is
42
+ # not installed, so its lib has to lead the search.
43
+ def source_tree_lib
44
+ lib = shim_dir.parent.join("lib")
45
+ lib if lib.join(VAULT_SOURCE).file?
46
+ end
47
+
48
+ # Registering these keeps the roots' specs discoverable to RubyGems, which is
49
+ # what lets `bundle plugin install` see a gem it has just written into the
50
+ # plugin root. It has to run on every evaluation, because it does not survive
51
+ # one: Gem.clear_paths and Gem::Specification.reset, both of which Bundler
52
+ # calls while configuring the bundle, recompute the dirs from Gem.path.
53
+ def register_spec_dirs
54
+ searched = Gem::Specification.dirs.map { |dir| Pathname(dir) }
55
+ unsearched = install_roots.map { |root| root.join("specifications") }.select(&:directory?) - searched
56
+ return refresh_stale_spec_cache if unsearched.empty?
57
+
58
+ Gem::Specification.dirs = (searched + unsearched).map { |dir| dir.parent.to_s }
59
+ end
60
+
61
+ # The searched dirs are right but the spec cache predates this install:
62
+ # `bundle plugin install` switches GEM_HOME to the plugin root and caches
63
+ # specs during resolution, before gemvault lands there.
64
+ def refresh_stale_spec_cache
65
+ Gem::Specification.find_by_name(GEM)
66
+ rescue Gem::LoadError
67
+ Gem::Specification.reset
68
+ end
69
+
70
+ def entries
71
+ spec = gemvault_spec
72
+ return [] unless spec
73
+
74
+ [*dependency_specs(spec).flat_map(&:full_require_paths), *spec.full_require_paths]
75
+ end
76
+
77
+ # Prefer the version the shim was released against, read off the shim's own
78
+ # spec rather than its directory name: that name is a version only for a
79
+ # plain `gem install`, and never for a source tree, a path: plugin, a git
80
+ # plugin or a platform gem. With no spec to read, take the newest gemvault
81
+ # present, which beats whichever the filesystem listed first.
82
+ def gemvault_spec
83
+ candidates = specs_named(GEM).select { |spec| vault_source_in?(spec) }
84
+ pinned = candidates.select { |spec| pinned_requirement.satisfied_by?(spec.version) }
85
+
86
+ (pinned.empty? ? candidates : pinned).max_by(&:version)
87
+ end
88
+
89
+ def pinned_requirement
90
+ dependency = shim_spec&.dependencies&.find { |dep| dep.name == GEM }
91
+
92
+ dependency&.requirement || Gem::Requirement.default
93
+ end
94
+
95
+ def shim_spec
96
+ specs_named(SHIM).find { |spec| spec.full_gem_path == shim_dir.to_s }
97
+ end
98
+
99
+ def dependency_specs(spec)
100
+ spec.runtime_dependencies.filter_map do |dependency|
101
+ specs_named(dependency.name)
102
+ .select { |candidate| dependency.requirement.satisfied_by?(candidate.version) }
103
+ .max_by(&:version)
104
+ end
105
+ end
106
+
107
+ def specs_named(name)
108
+ install_roots
109
+ .flat_map { |root| root.glob("specifications/#{name}-*.gemspec") }
110
+ .filter_map { |file| Gem::Specification.load(file.to_s) }
111
+ .select { |spec| spec.name == name }
112
+ end
113
+
114
+ def vault_source_in?(spec)
115
+ spec.full_require_paths.any? { |path| Pathname(path).join(VAULT_SOURCE).file? }
116
+ end
117
+
118
+ # Installed, plugins.rb lives at <gem root>/gems/bundler-source-vault-<v>/,
119
+ # and gemvault may sit in the same root: Bundler's local or global plugin
120
+ # root, or a plain GEM_HOME. Both plugin roots count, because
121
+ # Bundler::Plugin.root is the project-local root even when the plugin loads
122
+ # from the global one.
123
+ #
124
+ # Gem.default_path is the load-bearing entry. Bundler skips installing a
125
+ # plugin dependency that is already installed, so wherever `gem install
126
+ # gemvault` has run the plugin root holds the shim alone; once the app bundle
127
+ # is populated Bundler narrows GEM_PATH to it and the ambient gemvault drops
128
+ # out of Gem.path. Gem.default_path is what RubyGems knows about its own gem
129
+ # roots regardless of that narrowing. Reading roots back out of the
130
+ # environment does not cover it -- rubies from rbenv, asdf, chruby, Homebrew
131
+ # and distros export neither GEM_HOME nor GEM_PATH -- but it still matters
132
+ # for the ones that export a root outside those defaults, like RVM.
133
+ def install_roots
134
+ candidate_roots
135
+ .reject { |root| root.to_s.empty? }
136
+ .map { |root| Pathname(root.to_s) }
137
+ .uniq
138
+ .select { |root| root.join("specifications").directory? }
139
+ end
140
+
141
+ def candidate_roots
142
+ [shim_dir.parent.parent, *plugin_roots, *Gem.path, *Gem.default_path, *exported_roots]
143
+ end
144
+
145
+ def plugin_roots
146
+ return [] unless defined?(Bundler::Plugin)
147
+
148
+ [Bundler::Plugin.root, Bundler::Plugin.global_root]
149
+ end
150
+
151
+ def exported_roots
152
+ return [] unless defined?(Bundler) && Bundler.respond_to?(:original_env)
153
+
154
+ original = Bundler.original_env
155
+
156
+ [original["GEM_HOME"], *original["GEM_PATH"].to_s.split(File::PATH_SEPARATOR)]
157
+ end
158
+
159
+ def shim_dir
160
+ Pathname(__dir__)
161
+ end
162
+ end
163
+ end
data/plugins.rb CHANGED
@@ -1,23 +1,23 @@
1
- # Bundler installs plugin dependencies (e.g. sqlite3) into Plugin.root but
2
- # does not add their load paths before loading plugins.rb. This is a known
3
- # Bundler limitation -- see the "Currently not done to avoid conflicts" comment
4
- # in bundler/plugin.rb#load_plugin.
5
- #
6
- # Work around by registering Plugin.root as a gem search path so dependencies
7
- # installed there are activatable via `require`.
8
- # Development: when loaded from within the gemvault source tree, the gemvault
9
- # gem isn't installed so its lib/ must be on $LOAD_PATH for the require below.
10
- gemvault_lib = File.expand_path("../lib", __dir__)
11
- $LOAD_PATH.unshift(gemvault_lib) unless $LOAD_PATH.include?(gemvault_lib)
1
+ require_relative "gemvault_load_path"
12
2
 
13
- if defined?(Bundler::Plugin)
14
- plugin_root = Bundler::Plugin.root.to_s
15
- spec_dir = File.join(plugin_root, "specifications")
16
- if File.directory?(spec_dir) && !Gem::Specification.dirs.include?(spec_dir)
17
- Gem::Specification.dirs = Gem.path + [plugin_root]
18
- end
19
- end
3
+ # Runs on every evaluation. Bundler discards the registration while configuring
4
+ # the bundle, so it cannot sit behind the guard below.
5
+ BundlerSourceVault::GemvaultLoadPath.register_spec_dirs
6
+
7
+ # Bundler evaluates this file more than once per process: once when it saves a
8
+ # freshly installed plugin, again when the Gemfile asks for the source. Loading
9
+ # gemvault twice from two different roots is fatal rather than merely noisy,
10
+ # because Gemvault::GemEntry subclasses a fresh Data.define result each time, so
11
+ # a second definition raises a superclass mismatch. Resolve and require only on
12
+ # the first evaluation. The registration above and this one below still repeat --
13
+ # Bundler reads the source back out of Plugin::API after each load.
14
+ unless defined?(Bundler::Plugin::VaultSource)
15
+ source_tree_lib = BundlerSourceVault::GemvaultLoadPath.source_tree_lib
16
+ $LOAD_PATH.unshift(source_tree_lib.to_s) if source_tree_lib && !$LOAD_PATH.include?(source_tree_lib.to_s)
20
17
 
21
- require "bundler/plugin/vault_source"
18
+ BundlerSourceVault::GemvaultLoadPath.apply
19
+
20
+ require "bundler/plugin/vault_source"
21
+ end
22
22
 
23
23
  Bundler::Plugin::API.source("vault", Bundler::Plugin::VaultSource)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler-source-vault
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Gillis
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.1.5
18
+ version: 0.2.2
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.1.5
25
+ version: 0.2.2
26
26
  description: Registers the :vault source type with Bundler, enabling gem installation
27
27
  from .gemv vault files
28
28
  email:
@@ -31,6 +31,7 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - gemvault_load_path.rb
34
35
  - plugins.rb
35
36
  homepage: https://github.com/gillisd/gemvault
36
37
  licenses:
@@ -52,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
53
  - !ruby/object:Gem::Version
53
54
  version: '0'
54
55
  requirements: []
55
- rubygems_version: 4.0.16
56
+ rubygems_version: 4.0.17
56
57
  specification_version: 4
57
58
  summary: Bundler source plugin for gemvault
58
59
  test_files: []