bundler 4.0.21 → 4.1.0.beta1
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/bundler/vendor/connection_pool/LICENSE → MIT.txt} +8 -6
- data/bundler.gemspec +23 -6
- data/lib/bundler/build_metadata.rb +2 -2
- data/lib/bundler/checksum.rb +40 -25
- data/lib/bundler/ci_detector.rb +1 -1
- data/lib/bundler/cli/add.rb +3 -4
- data/lib/bundler/cli/cache.rb +4 -0
- data/lib/bundler/cli/clean.rb +5 -0
- data/lib/bundler/cli/common.rb +37 -1
- data/lib/bundler/cli/config.rb +4 -2
- data/lib/bundler/cli/gem.rb +0 -17
- data/lib/bundler/cli/install.rb +4 -5
- data/lib/bundler/cli/lock.rb +1 -2
- data/lib/bundler/cli/outdated.rb +8 -3
- data/lib/bundler/cli/pristine.rb +2 -1
- data/lib/bundler/cli/update.rb +21 -12
- data/lib/bundler/cli.rb +14 -11
- data/lib/bundler/definition.rb +61 -24
- data/lib/bundler/dependency.rb +4 -0
- data/lib/bundler/dsl.rb +77 -11
- data/lib/bundler/endpoint_specification.rb +51 -14
- data/lib/bundler/env.rb +7 -24
- data/lib/bundler/errors.rb +34 -5
- data/lib/bundler/fetcher/compact_index.rb +14 -5
- data/lib/bundler/fetcher/connection_pools.rb +150 -0
- data/lib/bundler/fetcher/downloader.rb +31 -16
- data/lib/bundler/fetcher.rb +8 -47
- data/lib/bundler/injector.rb +3 -3
- data/lib/bundler/inline.rb +44 -4
- data/lib/bundler/installer/parallel_installer.rb +25 -30
- data/lib/bundler/installer.rb +20 -6
- data/lib/bundler/lazy_specification.rb +44 -17
- data/lib/bundler/lockfile_generator.rb +24 -2
- data/lib/bundler/lockfile_parser.rb +43 -5
- data/lib/bundler/man/bundle-add.1 +7 -4
- data/lib/bundler/man/bundle-add.1.ronn +8 -4
- data/lib/bundler/man/bundle-binstubs.1 +1 -1
- data/lib/bundler/man/bundle-cache.1 +2 -2
- data/lib/bundler/man/bundle-cache.1.ronn +2 -2
- data/lib/bundler/man/bundle-check.1 +1 -1
- data/lib/bundler/man/bundle-clean.1 +1 -1
- data/lib/bundler/man/bundle-config.1 +38 -6
- data/lib/bundler/man/bundle-config.1.ronn +143 -10
- data/lib/bundler/man/bundle-console.1 +1 -1
- data/lib/bundler/man/bundle-doctor.1 +1 -1
- data/lib/bundler/man/bundle-env.1 +1 -1
- data/lib/bundler/man/bundle-exec.1 +1 -1
- data/lib/bundler/man/bundle-fund.1 +1 -1
- data/lib/bundler/man/bundle-gem.1 +1 -1
- data/lib/bundler/man/bundle-help.1 +1 -1
- data/lib/bundler/man/bundle-info.1 +1 -1
- data/lib/bundler/man/bundle-init.1 +1 -1
- data/lib/bundler/man/bundle-install.1 +2 -2
- data/lib/bundler/man/bundle-install.1.ronn +4 -4
- data/lib/bundler/man/bundle-issue.1 +1 -1
- data/lib/bundler/man/bundle-licenses.1 +1 -1
- data/lib/bundler/man/bundle-list.1 +1 -1
- data/lib/bundler/man/bundle-lock.1 +3 -3
- data/lib/bundler/man/bundle-lock.1.ronn +5 -5
- data/lib/bundler/man/bundle-open.1 +1 -1
- data/lib/bundler/man/bundle-outdated.1 +1 -1
- data/lib/bundler/man/bundle-platform.1 +1 -1
- data/lib/bundler/man/bundle-plugin.1 +1 -1
- data/lib/bundler/man/bundle-pristine.1 +1 -1
- data/lib/bundler/man/bundle-remove.1 +1 -1
- data/lib/bundler/man/bundle-show.1 +1 -1
- data/lib/bundler/man/bundle-update.1 +3 -3
- data/lib/bundler/man/bundle-update.1.ronn +3 -5
- data/lib/bundler/man/bundle-version.1 +1 -1
- data/lib/bundler/man/bundle.1 +1 -1
- data/lib/bundler/man/gemfile.5 +45 -1
- data/lib/bundler/man/gemfile.5.ronn +53 -0
- data/lib/bundler/match_metadata.rb +20 -0
- data/lib/bundler/match_platform.rb +25 -2
- data/lib/bundler/match_remote_metadata.rb +21 -6
- data/lib/bundler/materialization.rb +2 -1
- data/lib/bundler/override.rb +69 -0
- data/lib/bundler/plugin/dsl.rb +3 -5
- data/lib/bundler/plugin/events.rb +55 -13
- data/lib/bundler/plugin/index.rb +87 -11
- data/lib/bundler/plugin/installer/path.rb +1 -1
- data/lib/bundler/plugin/installer.rb +13 -5
- data/lib/bundler/plugin.rb +58 -30
- data/lib/bundler/remote_specification.rb +6 -3
- data/lib/bundler/resolver/base.rb +2 -1
- data/lib/bundler/resolver/incompatibility.rb +1 -1
- data/lib/bundler/resolver.rb +73 -23
- data/lib/bundler/ruby_version.rb +0 -2
- data/lib/bundler/rubygems_ext.rb +71 -1
- data/lib/bundler/rubygems_gem_installer.rb +41 -2
- data/lib/bundler/rubygems_integration.rb +7 -2
- data/lib/bundler/runtime.rb +69 -2
- data/lib/bundler/self_manager.rb +9 -15
- data/lib/bundler/settings.rb +373 -30
- data/lib/bundler/shared_helpers.rb +2 -1
- data/lib/bundler/source/git/git_proxy.rb +2 -10
- data/lib/bundler/source/git.rb +15 -6
- data/lib/bundler/source/path/installer.rb +1 -1
- data/lib/bundler/source/rubygems/remote.rb +10 -5
- data/lib/bundler/source/rubygems.rb +48 -12
- data/lib/bundler/spec_set.rb +16 -4
- data/lib/bundler/stub_specification.rb +2 -1
- data/lib/bundler/templates/newgem/Gemfile.tt +4 -4
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +3 -3
- data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -1
- data/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt +1 -1
- data/lib/bundler/ui/shell.rb +0 -4
- data/lib/bundler/ui/silent.rb +0 -4
- data/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +35 -2
- data/lib/bundler/vendor/thor/lib/thor/base.rb +2 -1
- data/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +14 -6
- data/lib/bundler/vendor/thor/lib/thor/shell/color.rb +2 -0
- data/lib/bundler/vendor/thor/lib/thor/version.rb +1 -1
- data/lib/bundler/vendor/thor/lib/thor.rb +34 -1
- data/lib/bundler/vendored_pub_grub.rb +5 -2
- data/lib/bundler/vendored_securerandom.rb +11 -8
- data/lib/bundler/vendored_uri.rb +14 -14
- data/lib/bundler/version.rb +1 -1
- data/lib/bundler/worker.rb +3 -2
- data/lib/bundler.rb +1 -1
- data/lib/rubygems/compact_index_client/cache.rb +122 -0
- data/lib/rubygems/compact_index_client/cache_file.rb +147 -0
- data/lib/rubygems/compact_index_client/http_fetcher.rb +88 -0
- data/lib/rubygems/compact_index_client/parser.rb +90 -0
- data/lib/rubygems/compact_index_client/updater.rb +113 -0
- data/lib/rubygems/compact_index_client.rb +111 -0
- data/lib/rubygems/cooldown_settings.rb +57 -0
- data/lib/rubygems/credential_store/native/linux.rb +97 -0
- data/lib/rubygems/credential_store/native/macos.rb +88 -0
- data/lib/rubygems/credential_store/native/windows.rb +130 -0
- data/lib/rubygems/credential_store.rb +332 -0
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/assignment.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +7 -7
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/failure_writer.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/incompatibility.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/package.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/partial_solution.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/rubygems.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/solve_failure.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/static_package_source.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/strategy.rb +2 -2
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/term.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_constraint.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_range.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_solver.rb +2 -2
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_union.rb +1 -1
- data/lib/rubygems/vendor/pub_grub/lib/pub_grub.rb +57 -0
- data/lib/{bundler → rubygems}/vendor/securerandom/lib/securerandom.rb +10 -6
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/common.rb +119 -119
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/file.rb +13 -13
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ftp.rb +24 -24
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/generic.rb +132 -132
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/http.rb +18 -18
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/https.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldap.rb +14 -14
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldaps.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/mailto.rb +21 -21
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc2396_parser.rb +39 -39
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc3986_parser.rb +14 -14
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/version.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ws.rb +12 -12
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/wss.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri.rb +35 -31
- data/lib/rubygems/yaml_serializer.rb +880 -0
- metadata +77 -68
- data/CHANGELOG.md +0 -5784
- data/LICENSE.md +0 -22
- data/README.md +0 -58
- data/lib/bundler/compact_index_client/cache.rb +0 -107
- data/lib/bundler/compact_index_client/cache_file.rb +0 -148
- data/lib/bundler/compact_index_client/parser.rb +0 -87
- data/lib/bundler/compact_index_client/updater.rb +0 -105
- data/lib/bundler/compact_index_client.rb +0 -92
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +0 -227
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +0 -3
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb +0 -56
- data/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +0 -230
- data/lib/bundler/vendor/net-http-persistent/README.rdoc +0 -82
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb +0 -41
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb +0 -65
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb +0 -80
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +0 -1153
- data/lib/bundler/vendor/pub_grub/lib/pub_grub.rb +0 -31
- data/lib/bundler/vendored_persistent.rb +0 -11
- data/lib/bundler/yaml_serializer.rb +0 -98
- /data/lib/{bundler → rubygems}/vendor/pub_grub/LICENSE.txt +0 -0
- /data/lib/{bundler → rubygems}/vendor/securerandom/COPYING +0 -0
- /data/lib/{bundler → rubygems}/vendor/uri/COPYING +0 -0
data/LICENSE.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
The MIT License
|
|
2
|
-
|
|
3
|
-
Portions copyright (c) 2010-2019 André Arko
|
|
4
|
-
Portions copyright (c) 2009 Engine Yard
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in
|
|
14
|
-
all copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
-
THE SOFTWARE.
|
data/README.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
[](https://rubygems.org/gems/bundler)
|
|
2
|
-
|
|
3
|
-
# Bundler: a gem to bundle gems
|
|
4
|
-
|
|
5
|
-
Bundler makes sure Ruby applications run the same code on every machine.
|
|
6
|
-
|
|
7
|
-
It does this by managing the gems that the application depends on. Given a list of gems, it can automatically download and install those gems, as well as any other gems needed by the gems that are listed. Before installing gems, it checks the versions of every gem to make sure that they are compatible, and can all be loaded at the same time. After the gems have been installed, Bundler can help you update some or all of them when new versions become available. Finally, it records the exact versions that have been installed, so that others can install the exact same gems.
|
|
8
|
-
|
|
9
|
-
## Installation and usage
|
|
10
|
-
|
|
11
|
-
To install (or update to the latest version):
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
gem install bundler
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
To install a prerelease version (if one is available), run `gem install bundler --pre`. To uninstall Bundler, run `gem uninstall bundler`.
|
|
18
|
-
|
|
19
|
-
Bundler is most commonly used to manage your application's dependencies. For example, these commands will allow you to use Bundler to manage the `rspec` gem for your application:
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
bundle init
|
|
23
|
-
bundle add rspec
|
|
24
|
-
bundle install
|
|
25
|
-
bundle exec rspec
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
See [bundler.io](https://bundler.io) for the full documentation.
|
|
29
|
-
|
|
30
|
-
## Troubleshooting
|
|
31
|
-
|
|
32
|
-
For help with common problems, see [TROUBLESHOOTING](../doc/bundler/TROUBLESHOOTING.md).
|
|
33
|
-
|
|
34
|
-
Still stuck? Try [filing an issue](https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md).
|
|
35
|
-
|
|
36
|
-
## Other questions
|
|
37
|
-
|
|
38
|
-
To see what has changed in recent versions of Bundler, see the [CHANGELOG](CHANGELOG.md).
|
|
39
|
-
|
|
40
|
-
To get in touch with the Bundler core team and other Bundler users, please join [the Bundler slack](https://join.slack.com/t/bundler/shared_invite/zt-1rrsuuv3m-OmXKWQf8K6iSla4~F1DBjQ).
|
|
41
|
-
|
|
42
|
-
## Contributing
|
|
43
|
-
|
|
44
|
-
If you'd like to contribute to Bundler, that's awesome, and we <3 you. We've put together [the Bundler contributor guide](https://github.com/ruby/rubygems/blob/master/doc/bundler/contributing/README.md) with all of the information you need to get started.
|
|
45
|
-
|
|
46
|
-
If you'd like to request a substantial change to Bundler or its documentation, refer to the [Bundler RFC process](https://github.com/rubygems/rfcs) for more information.
|
|
47
|
-
|
|
48
|
-
## Supporting
|
|
49
|
-
|
|
50
|
-
RubyGems is managed by [Ruby Central](https://rubycentral.org), a non-profit organization that supports the Ruby community through projects like this one, as well as [RubyConf](https://rubyconf.org), [RailsConf](https://railsconf.org), and [RubyGems.org](https://rubygems.org). You can support Ruby Central by attending or [sponsoring](sponsors@rubycentral.org) a conference, or by [joining as a supporting member](https://rubycentral.org/#/portal/signup).
|
|
51
|
-
|
|
52
|
-
## Code of Conduct
|
|
53
|
-
|
|
54
|
-
Everyone interacting in the Bundler project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [Bundler code of conduct](https://github.com/ruby/rubygems/blob/master/CODE_OF_CONDUCT.md).
|
|
55
|
-
|
|
56
|
-
## License
|
|
57
|
-
|
|
58
|
-
Bundler is available under an [MIT License](https://github.com/ruby/rubygems/blob/master/bundler/LICENSE.md).
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rubygems/resolver/api_set/gem_parser"
|
|
4
|
-
|
|
5
|
-
module Bundler
|
|
6
|
-
class CompactIndexClient
|
|
7
|
-
class Cache
|
|
8
|
-
attr_reader :directory
|
|
9
|
-
|
|
10
|
-
def initialize(directory, fetcher = nil)
|
|
11
|
-
@directory = Pathname.new(directory).expand_path
|
|
12
|
-
@updater = Updater.new(fetcher) if fetcher
|
|
13
|
-
@mutex = Thread::Mutex.new
|
|
14
|
-
@endpoints = Set.new
|
|
15
|
-
|
|
16
|
-
@info_root = mkdir("info")
|
|
17
|
-
@special_characters_info_root = mkdir("info-special-characters")
|
|
18
|
-
@info_etag_root = mkdir("info-etags")
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def names
|
|
22
|
-
fetch("names", names_path, names_etag_path)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def versions
|
|
26
|
-
fetch("versions", versions_path, versions_etag_path)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def info(name, remote_checksum = nil)
|
|
30
|
-
path = info_path(name)
|
|
31
|
-
|
|
32
|
-
if remote_checksum && remote_checksum != SharedHelpers.checksum_for_file(path, :MD5)
|
|
33
|
-
fetch("info/#{name}", path, info_etag_path(name))
|
|
34
|
-
else
|
|
35
|
-
Bundler::CompactIndexClient.debug { "update skipped info/#{name} (#{remote_checksum ? "versions index checksum is nil" : "versions index checksum matches local"})" }
|
|
36
|
-
read(path)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def reset!
|
|
41
|
-
@mutex.synchronize { @endpoints.clear }
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
def names_path = directory.join("names")
|
|
47
|
-
def names_etag_path = directory.join("names.etag")
|
|
48
|
-
def versions_path = directory.join("versions")
|
|
49
|
-
def versions_etag_path = directory.join("versions.etag")
|
|
50
|
-
|
|
51
|
-
def info_path(name)
|
|
52
|
-
name = name.to_s
|
|
53
|
-
validate_name!(name)
|
|
54
|
-
# TODO: converge this into the info_root by hashing all filenames like info_etag_path
|
|
55
|
-
if /[^a-z0-9_-]/.match?(name)
|
|
56
|
-
name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}"
|
|
57
|
-
@special_characters_info_root.join(name)
|
|
58
|
-
else
|
|
59
|
-
@info_root.join(name)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def info_etag_path(name)
|
|
64
|
-
name = name.to_s
|
|
65
|
-
validate_name!(name)
|
|
66
|
-
@info_etag_root.join("#{name}-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}")
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Gem names come from the remote index and are not otherwise
|
|
70
|
-
# validated, so refuse anything that would escape the cache
|
|
71
|
-
# directory when used as a path component.
|
|
72
|
-
def validate_name!(name)
|
|
73
|
-
return if File.basename(name) == name
|
|
74
|
-
|
|
75
|
-
raise SecurityError, "malformed gem name: #{name.inspect}"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def mkdir(name)
|
|
79
|
-
directory.join(name).tap do |dir|
|
|
80
|
-
SharedHelpers.filesystem_access(dir) do
|
|
81
|
-
FileUtils.mkdir_p(dir)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def fetch(remote_path, path, etag_path)
|
|
87
|
-
if already_fetched?(remote_path)
|
|
88
|
-
Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
|
|
89
|
-
else
|
|
90
|
-
Bundler::CompactIndexClient.debug { "fetching #{remote_path}" }
|
|
91
|
-
@updater&.update(remote_path, path, etag_path)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
read(path)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def already_fetched?(remote_path)
|
|
98
|
-
@mutex.synchronize { !@endpoints.add?(remote_path) }
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def read(path)
|
|
102
|
-
return unless path.file?
|
|
103
|
-
SharedHelpers.filesystem_access(path, :read, &:read)
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "../vendored_fileutils"
|
|
4
|
-
require "rubygems/package"
|
|
5
|
-
|
|
6
|
-
module Bundler
|
|
7
|
-
class CompactIndexClient
|
|
8
|
-
# write cache files in a way that is robust to concurrent modifications
|
|
9
|
-
# if digests are given, the checksums will be verified
|
|
10
|
-
class CacheFile
|
|
11
|
-
DEFAULT_FILE_MODE = 0o644
|
|
12
|
-
private_constant :DEFAULT_FILE_MODE
|
|
13
|
-
|
|
14
|
-
class Error < RuntimeError; end
|
|
15
|
-
class ClosedError < Error; end
|
|
16
|
-
|
|
17
|
-
class DigestMismatchError < Error
|
|
18
|
-
def initialize(digests, expected_digests)
|
|
19
|
-
super "Calculated checksums #{digests.inspect} did not match expected #{expected_digests.inspect}."
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Initialize with a copy of the original file, then yield the instance.
|
|
24
|
-
def self.copy(path, &block)
|
|
25
|
-
new(path) do |file|
|
|
26
|
-
file.initialize_digests
|
|
27
|
-
|
|
28
|
-
SharedHelpers.filesystem_access(path, :read) do
|
|
29
|
-
path.open("rb") do |s|
|
|
30
|
-
file.open {|f| IO.copy_stream(s, f) }
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
yield file
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Write data to a temp file, then replace the original file with it verifying the digests if given.
|
|
39
|
-
def self.write(path, data, digests = nil)
|
|
40
|
-
return unless data
|
|
41
|
-
new(path) do |file|
|
|
42
|
-
file.digests = digests
|
|
43
|
-
file.write(data)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
attr_reader :original_path, :path
|
|
48
|
-
|
|
49
|
-
def initialize(original_path, &block)
|
|
50
|
-
@original_path = original_path
|
|
51
|
-
@perm = original_path.file? ? original_path.stat.mode : DEFAULT_FILE_MODE
|
|
52
|
-
@path = original_path.sub(/$/, ".#{$$}.tmp")
|
|
53
|
-
return unless block_given?
|
|
54
|
-
begin
|
|
55
|
-
yield self
|
|
56
|
-
ensure
|
|
57
|
-
close
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def size
|
|
62
|
-
path.size
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# initialize the digests using CompactIndexClient::SUPPORTED_DIGESTS, or a subset based on keys.
|
|
66
|
-
def initialize_digests(keys = nil)
|
|
67
|
-
@digests = keys ? SUPPORTED_DIGESTS.slice(*keys) : SUPPORTED_DIGESTS.dup
|
|
68
|
-
@digests.transform_values! {|algo_class| SharedHelpers.digest(algo_class).new }
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# reset the digests so they don't contain any previously read data
|
|
72
|
-
def reset_digests
|
|
73
|
-
@digests&.each_value(&:reset)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# set the digests that will be verified at the end
|
|
77
|
-
def digests=(expected_digests)
|
|
78
|
-
@expected_digests = expected_digests
|
|
79
|
-
|
|
80
|
-
if @expected_digests.nil?
|
|
81
|
-
@digests = nil
|
|
82
|
-
elsif @digests
|
|
83
|
-
@digests = @digests.slice(*@expected_digests.keys)
|
|
84
|
-
else
|
|
85
|
-
initialize_digests(@expected_digests.keys)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def digests?
|
|
90
|
-
@digests&.any?
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Open the temp file for writing, reusing original permissions, yielding the IO object.
|
|
94
|
-
def open(write_mode = "wb", perm = @perm, &block)
|
|
95
|
-
raise ClosedError, "Cannot reopen closed file" if @closed
|
|
96
|
-
SharedHelpers.filesystem_access(path, :write) do
|
|
97
|
-
path.open(write_mode, perm) do |f|
|
|
98
|
-
yield digests? ? Gem::Package::DigestIO.new(f, @digests) : f
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Returns false without appending when no digests since appending is too error prone to do without digests.
|
|
104
|
-
def append(data)
|
|
105
|
-
return false unless digests?
|
|
106
|
-
open("ab") {|f| f.write data }
|
|
107
|
-
verify && commit
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def write(data)
|
|
111
|
-
reset_digests
|
|
112
|
-
open {|f| f.write data }
|
|
113
|
-
commit!
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def commit!
|
|
117
|
-
verify || raise(DigestMismatchError.new(@base64digests, @expected_digests))
|
|
118
|
-
commit
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Verify the digests, returning true on match, false on mismatch.
|
|
122
|
-
def verify
|
|
123
|
-
return true unless @expected_digests && digests?
|
|
124
|
-
@base64digests = @digests.transform_values!(&:base64digest)
|
|
125
|
-
@digests = nil
|
|
126
|
-
@base64digests.all? {|algo, digest| @expected_digests[algo] == digest }
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Replace the original file with the temp file without verifying digests.
|
|
130
|
-
# The file is permanently closed.
|
|
131
|
-
def commit
|
|
132
|
-
raise ClosedError, "Cannot commit closed file" if @closed
|
|
133
|
-
SharedHelpers.filesystem_access(original_path, :write) do
|
|
134
|
-
FileUtils.mv(path, original_path)
|
|
135
|
-
end
|
|
136
|
-
@closed = true
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Remove the temp file without replacing the original file.
|
|
140
|
-
# The file is permanently closed.
|
|
141
|
-
def close
|
|
142
|
-
return if @closed
|
|
143
|
-
FileUtils.remove_file(path) if @path&.file?
|
|
144
|
-
@closed = true
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Bundler
|
|
4
|
-
class CompactIndexClient
|
|
5
|
-
class Parser
|
|
6
|
-
# `compact_index` - an object responding to #names, #versions, #info(name, checksum),
|
|
7
|
-
# returning the file contents as a string
|
|
8
|
-
def initialize(compact_index)
|
|
9
|
-
@compact_index = compact_index
|
|
10
|
-
@info_checksums = nil
|
|
11
|
-
@versions_by_name = nil
|
|
12
|
-
@available = nil
|
|
13
|
-
@gem_parser = nil
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def names
|
|
17
|
-
lines(@compact_index.names)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def versions
|
|
21
|
-
@versions_by_name ||= Hash.new {|hash, key| hash[key] = [] }
|
|
22
|
-
@info_checksums = {}
|
|
23
|
-
|
|
24
|
-
lines(@compact_index.versions).each do |line|
|
|
25
|
-
name, versions_string, checksum = line.split(" ", 3)
|
|
26
|
-
@info_checksums[name] = checksum || ""
|
|
27
|
-
versions_string.split(",") do |version|
|
|
28
|
-
delete = version.delete_prefix!("-")
|
|
29
|
-
version = version.split("-", 2).unshift(name)
|
|
30
|
-
if delete
|
|
31
|
-
@versions_by_name[name].delete(version)
|
|
32
|
-
else
|
|
33
|
-
@versions_by_name[name] << version
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
@versions_by_name
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def info(name)
|
|
42
|
-
data = @compact_index.info(name, info_checksums[name])
|
|
43
|
-
lines(data).map {|line| gem_parser.parse(line).unshift(name) }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def available?
|
|
47
|
-
return @available unless @available.nil?
|
|
48
|
-
@available = !info_checksums.empty?
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
def info_checksums
|
|
54
|
-
@info_checksums ||= lines(@compact_index.versions).each_with_object({}) do |line, checksums|
|
|
55
|
-
parse_version_checksum(line, checksums)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def lines(data)
|
|
60
|
-
return [] if data.nil? || data.empty?
|
|
61
|
-
lines = data.split("\n")
|
|
62
|
-
header = lines.index("---")
|
|
63
|
-
header ? lines[header + 1..-1] : lines
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def gem_parser
|
|
67
|
-
@gem_parser ||= Gem::Resolver::APISet::GemParser.new
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# This is mostly the same as `split(" ", 3)` but it avoids allocating extra objects.
|
|
71
|
-
# This method gets called at least once for every gem when parsing versions.
|
|
72
|
-
def parse_version_checksum(line, checksums)
|
|
73
|
-
return unless (name_end = line.index(" ")) # Artifactory bug causes blank lines in artifactor index files
|
|
74
|
-
checksum_start = line.index(" ", name_end + 1)
|
|
75
|
-
return unless checksum_start
|
|
76
|
-
checksum_start += 1
|
|
77
|
-
|
|
78
|
-
checksum_end = line.size - checksum_start
|
|
79
|
-
|
|
80
|
-
line.freeze # allows slicing into the string to not allocate a copy of the line
|
|
81
|
-
name = line[0, name_end]
|
|
82
|
-
checksum = line[checksum_start, checksum_end]
|
|
83
|
-
checksums[name.freeze] = checksum # freeze name since it is used as a hash key
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Bundler
|
|
4
|
-
class CompactIndexClient
|
|
5
|
-
class Updater
|
|
6
|
-
class MismatchedChecksumError < Error
|
|
7
|
-
def initialize(path, message)
|
|
8
|
-
super "The checksum of /#{path} does not match the checksum provided by the server! Something is wrong. #{message}"
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def initialize(fetcher)
|
|
13
|
-
@fetcher = fetcher
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def update(remote_path, local_path, etag_path)
|
|
17
|
-
append(remote_path, local_path, etag_path) || replace(remote_path, local_path, etag_path)
|
|
18
|
-
rescue CacheFile::DigestMismatchError => e
|
|
19
|
-
raise MismatchedChecksumError.new(remote_path, e.message)
|
|
20
|
-
rescue Zlib::GzipFile::Error
|
|
21
|
-
raise Bundler::HTTPError
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
def append(remote_path, local_path, etag_path)
|
|
27
|
-
return false unless local_path.file? && local_path.size.nonzero?
|
|
28
|
-
|
|
29
|
-
CacheFile.copy(local_path) do |file|
|
|
30
|
-
etag = etag_path.read.tap(&:chomp!) if etag_path.file?
|
|
31
|
-
|
|
32
|
-
# Subtract a byte to ensure the range won't be empty.
|
|
33
|
-
# Avoids 416 (Range Not Satisfiable) responses.
|
|
34
|
-
response = @fetcher.call(remote_path, request_headers(etag, file.size - 1))
|
|
35
|
-
break true if response.is_a?(Gem::Net::HTTPNotModified)
|
|
36
|
-
|
|
37
|
-
file.digests = parse_digests(response)
|
|
38
|
-
# server may ignore Range and return the full response
|
|
39
|
-
if response.is_a?(Gem::Net::HTTPPartialContent)
|
|
40
|
-
tail = response.body.byteslice(1..-1)
|
|
41
|
-
break false unless tail && file.append(tail)
|
|
42
|
-
else
|
|
43
|
-
file.write(response.body)
|
|
44
|
-
end
|
|
45
|
-
CacheFile.write(etag_path, etag_from_response(response))
|
|
46
|
-
true
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# request without range header to get the full file or a 304 Not Modified
|
|
51
|
-
def replace(remote_path, local_path, etag_path)
|
|
52
|
-
etag = etag_path.read.tap(&:chomp!) if etag_path.file?
|
|
53
|
-
response = @fetcher.call(remote_path, request_headers(etag))
|
|
54
|
-
return true if response.is_a?(Gem::Net::HTTPNotModified)
|
|
55
|
-
CacheFile.write(local_path, response.body, parse_digests(response))
|
|
56
|
-
CacheFile.write(etag_path, etag_from_response(response))
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def request_headers(etag, range_start = nil)
|
|
60
|
-
headers = {}
|
|
61
|
-
headers["Range"] = "bytes=#{range_start}-" if range_start
|
|
62
|
-
headers["If-None-Match"] = %("#{etag}") if etag
|
|
63
|
-
headers
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def etag_for_request(etag_path)
|
|
67
|
-
etag_path.read.tap(&:chomp!) if etag_path.file?
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def etag_from_response(response)
|
|
71
|
-
return unless response["ETag"]
|
|
72
|
-
etag = response["ETag"].delete_prefix("W/")
|
|
73
|
-
return if etag.delete_prefix!('"') && !etag.delete_suffix!('"')
|
|
74
|
-
etag
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Unwraps and returns a Hash of digest algorithms and base64 values
|
|
78
|
-
# according to RFC 8941 Structured Field Values for HTTP.
|
|
79
|
-
# https://www.rfc-editor.org/rfc/rfc8941#name-parsing-a-byte-sequence
|
|
80
|
-
# Ignores unsupported algorithms.
|
|
81
|
-
def parse_digests(response)
|
|
82
|
-
return unless header = response["Repr-Digest"] || response["Digest"]
|
|
83
|
-
digests = {}
|
|
84
|
-
header.split(",") do |param|
|
|
85
|
-
algorithm, value = param.split("=", 2)
|
|
86
|
-
algorithm.strip!
|
|
87
|
-
algorithm.downcase!
|
|
88
|
-
next unless SUPPORTED_DIGESTS.key?(algorithm)
|
|
89
|
-
next unless value = byte_sequence(value)
|
|
90
|
-
digests[algorithm] = value
|
|
91
|
-
end
|
|
92
|
-
digests.empty? ? nil : digests
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Unwrap surrounding colons (byte sequence)
|
|
96
|
-
# The wrapping characters must be matched or we return nil.
|
|
97
|
-
# Also handles quotes because right now rubygems.org sends them.
|
|
98
|
-
def byte_sequence(value)
|
|
99
|
-
return if value.delete_prefix!(":") && !value.delete_suffix!(":")
|
|
100
|
-
return if value.delete_prefix!('"') && !value.delete_suffix!('"')
|
|
101
|
-
value
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
|
-
module Bundler
|
|
6
|
-
# The CompactIndexClient is responsible for fetching and parsing the compact index.
|
|
7
|
-
#
|
|
8
|
-
# The compact index is a set of caching optimized files that are used to fetch gem information.
|
|
9
|
-
# The files are:
|
|
10
|
-
# - names: a list of all gem names
|
|
11
|
-
# - versions: a list of all gem versions
|
|
12
|
-
# - info/[gem]: a list of all versions of a gem
|
|
13
|
-
#
|
|
14
|
-
# The client is instantiated with:
|
|
15
|
-
# - `directory`: the root directory where the cache files are stored.
|
|
16
|
-
# - `fetcher`: (optional) an object that responds to #call(uri_path, headers) and returns an http response.
|
|
17
|
-
# If the `fetcher` is not provided, the client will only read cached files from disk.
|
|
18
|
-
#
|
|
19
|
-
# The client is organized into:
|
|
20
|
-
# - `Updater`: updates the cached files on disk using the fetcher.
|
|
21
|
-
# - `Cache`: calls the updater, caches files, read and return them from disk
|
|
22
|
-
# - `Parser`: parses the compact index file data
|
|
23
|
-
# - `CacheFile`: a concurrency safe file reader/writer that verifies checksums
|
|
24
|
-
#
|
|
25
|
-
# The client is intended to optimize memory usage and performance.
|
|
26
|
-
# It is called 100s or 1000s of times, parsing files with hundreds of thousands of lines.
|
|
27
|
-
# It may be called concurrently without global interpreter lock in some Rubies.
|
|
28
|
-
# As a result, some methods may look more complex than necessary to save memory or time.
|
|
29
|
-
class CompactIndexClient
|
|
30
|
-
SUPPORTED_DIGESTS = { "sha-256" => :SHA256 }.freeze
|
|
31
|
-
DEBUG_MUTEX = Thread::Mutex.new
|
|
32
|
-
|
|
33
|
-
# info returns an Array of INFO Arrays. Each INFO Array has the following indices:
|
|
34
|
-
INFO_NAME = 0
|
|
35
|
-
INFO_VERSION = 1
|
|
36
|
-
INFO_PLATFORM = 2
|
|
37
|
-
INFO_DEPS = 3
|
|
38
|
-
INFO_REQS = 4
|
|
39
|
-
|
|
40
|
-
def self.debug
|
|
41
|
-
return unless ENV["DEBUG_COMPACT_INDEX"]
|
|
42
|
-
DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
class Error < StandardError; end
|
|
46
|
-
|
|
47
|
-
require_relative "compact_index_client/cache"
|
|
48
|
-
require_relative "compact_index_client/cache_file"
|
|
49
|
-
require_relative "compact_index_client/parser"
|
|
50
|
-
require_relative "compact_index_client/updater"
|
|
51
|
-
|
|
52
|
-
def initialize(directory, fetcher = nil)
|
|
53
|
-
@cache = Cache.new(directory, fetcher)
|
|
54
|
-
@parser = Parser.new(@cache)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def names
|
|
58
|
-
Bundler::CompactIndexClient.debug { "names" }
|
|
59
|
-
@parser.names
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def versions
|
|
63
|
-
Bundler::CompactIndexClient.debug { "versions" }
|
|
64
|
-
@parser.versions
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def dependencies(names)
|
|
68
|
-
Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
|
|
69
|
-
names.map {|name| info(name) }
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def info(name)
|
|
73
|
-
Bundler::CompactIndexClient.debug { "info(#{name})" }
|
|
74
|
-
@parser.info(name)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def latest_version(name)
|
|
78
|
-
Bundler::CompactIndexClient.debug { "latest_version(#{name})" }
|
|
79
|
-
@parser.info(name).map {|d| Gem::Version.new(d[INFO_VERSION]) }.max
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def available?
|
|
83
|
-
Bundler::CompactIndexClient.debug { "available?" }
|
|
84
|
-
@parser.available?
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def reset!
|
|
88
|
-
Bundler::CompactIndexClient.debug { "reset!" }
|
|
89
|
-
@cache.reset!
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|