зеркало из https://github.com/github/ruby.git
Added bundler as default gems. Revisit [Feature #12733]
* bin/*, lib/bundler/*, lib/bundler.rb, spec/bundler, man/*: Merge from latest stable branch of bundler/bundler repository and added workaround patches. I will backport them into upstream. * common.mk, defs/gmake.mk: Added `test-bundler` task for test suite of bundler. * tool/sync_default_gems.rb: Added sync task for bundler. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@65509 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
Родитель
7deb37777a
Коммит
59c8d50653
30
LEGAL
30
LEGAL
|
@ -848,3 +848,33 @@ test/rubygems:
|
|||
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE.
|
||||
|
||||
lib/bundler:
|
||||
lib/bundler.rb:
|
||||
lib/bundler.gemspec:
|
||||
spec/bundler:
|
||||
man/bundle-*,gemfile.*:
|
||||
|
||||
Portions copyright (c) 2010 Andre Arko
|
||||
Portions copyright (c) 2009 Engine Yard
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
6
NEWS
6
NEWS
|
@ -391,6 +391,12 @@ sufficient information, see the ChangeLog file or Redmine
|
|||
|
||||
* Add URI::File to handle file URI scheme. [Feature #14035]
|
||||
|
||||
[Bundler]
|
||||
|
||||
* Add Bundler to Standard Library. [Feature #12733]
|
||||
|
||||
* Use 1.17.1. It's latest stable version.
|
||||
|
||||
=== Compatibility issues (excluding feature bug fixes)
|
||||
|
||||
[File]
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Exit cleanly from an early interrupt
|
||||
Signal.trap("INT") do
|
||||
Bundler.ui.debug("\n#{caller.join("\n")}") if defined?(Bundler)
|
||||
exit 1
|
||||
end
|
||||
|
||||
require "bundler"
|
||||
# Check if an older version of bundler is installed
|
||||
$LOAD_PATH.each do |path|
|
||||
next unless path =~ %r{/bundler-0\.(\d+)} && $1.to_i < 9
|
||||
err = String.new
|
||||
err << "Looks like you have a version of bundler that's older than 0.9.\n"
|
||||
err << "Please remove your old versions.\n"
|
||||
err << "An easy way to do this is by running `gem cleanup bundler`."
|
||||
abort(err)
|
||||
end
|
||||
|
||||
require "bundler/friendly_errors"
|
||||
Bundler.with_friendly_errors do
|
||||
require "bundler/cli"
|
||||
|
||||
# Allow any command to use --help flag to show help for that command
|
||||
help_flags = %w[--help -h]
|
||||
help_flag_used = ARGV.any? {|a| help_flags.include? a }
|
||||
args = help_flag_used ? Bundler::CLI.reformatted_help_args(ARGV) : ARGV
|
||||
|
||||
Bundler::CLI.start(args, :debug => true)
|
||||
end
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/shared_helpers"
|
||||
|
||||
Bundler::SharedHelpers.major_deprecation(2, "the bundle_ruby executable has been removed in favor of `bundle platform --ruby`")
|
||||
|
||||
Signal.trap("INT") { exit 1 }
|
||||
|
||||
require "bundler/errors"
|
||||
require "bundler/ruby_version"
|
||||
require "bundler/ruby_dsl"
|
||||
|
||||
module Bundler
|
||||
class Dsl
|
||||
include RubyDsl
|
||||
|
||||
attr_accessor :ruby_version
|
||||
|
||||
def initialize
|
||||
@ruby_version = nil
|
||||
end
|
||||
|
||||
def eval_gemfile(gemfile, contents = nil)
|
||||
contents ||= File.open(gemfile, "rb", &:read)
|
||||
instance_eval(contents, gemfile.to_s, 1)
|
||||
rescue SyntaxError => e
|
||||
bt = e.message.split("\n")[1..-1]
|
||||
raise GemfileError, ["Gemfile syntax error:", *bt].join("\n")
|
||||
rescue ScriptError, RegexpError, NameError, ArgumentError => e
|
||||
e.backtrace[0] = "#{e.backtrace[0]}: #{e.message} (#{e.class})"
|
||||
STDERR.puts e.backtrace.join("\n ")
|
||||
raise GemfileError, "There was an error in your Gemfile," \
|
||||
" and Bundler cannot continue."
|
||||
end
|
||||
|
||||
def source(source, options = {})
|
||||
end
|
||||
|
||||
def gem(name, *args)
|
||||
end
|
||||
|
||||
def group(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dsl = Bundler::Dsl.new
|
||||
begin
|
||||
dsl.eval_gemfile(Bundler::SharedHelpers.default_gemfile)
|
||||
ruby_version = dsl.ruby_version
|
||||
if ruby_version
|
||||
puts ruby_version
|
||||
else
|
||||
puts "No ruby version specified"
|
||||
end
|
||||
rescue Bundler::GemfileError => e
|
||||
puts e.message
|
||||
exit(-1)
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
load File.expand_path("../bundle", __FILE__)
|
16
common.mk
16
common.mk
|
@ -1232,6 +1232,21 @@ yes-test-bundled-gems: test-bundled-gems-run
|
|||
no-test-bundled-gems:
|
||||
test-bundled-gems-run: $(PREPARE_BUNDLED_GEMS)
|
||||
|
||||
test-bundler-precheck: $(arch)-fake.rb programs
|
||||
|
||||
yes-test-bundler-prepare: test-bundler-precheck
|
||||
$(XRUBY) -C "$(srcdir)" bin/gem install --no-document \
|
||||
--install-dir .bundle --conservative "rspec:~> 3.5"
|
||||
|
||||
RSPECOPTS = --format progress
|
||||
BUNDLER_SPECS =
|
||||
test-bundler: $(TEST_RUNNABLE)-test-bundler
|
||||
yes-test-bundler: yes-test-bundler-prepare
|
||||
$(gnumake_recursive)$(Q) \
|
||||
$(XRUBY) -C $(srcdir) -Ispec/bundler .bundle/bin/rspec \
|
||||
--require spec_helper $(RSPECOPTS) spec/bundler/$(BUNDLER_SPECS)
|
||||
no-test-bundler:
|
||||
|
||||
UNICODE_FILES = $(UNICODE_SRC_DATA_DIR)/UnicodeData.txt \
|
||||
$(UNICODE_SRC_DATA_DIR)/CompositionExclusions.txt \
|
||||
$(UNICODE_SRC_DATA_DIR)/NormalizationTest.txt \
|
||||
|
@ -1403,6 +1418,7 @@ help: PHONY
|
|||
" test-all: all ruby tests [TESTOPTS=-j4 TESTS=<test files>]" \
|
||||
" test-spec: run the Ruby spec suite" \
|
||||
" test-rubyspec: same as test-spec" \
|
||||
" test-bundler: run the Bundler spec" \
|
||||
" test-bundled-gems: run the test suite of bundled gems" \
|
||||
" up: update local copy and autogenerated files" \
|
||||
" benchmark: benchmark this ruby and COMPARE_RUBY." \
|
||||
|
|
|
@ -64,7 +64,7 @@ endif
|
|||
ORDERED_TEST_TARGETS := $(filter $(TEST_TARGETS), \
|
||||
btest-ruby test-knownbug test-basic \
|
||||
test-testframework test-ruby test-almost test-all \
|
||||
test-spec \
|
||||
test-spec test-bundler-prepare test-bundler \
|
||||
)
|
||||
prev_test := $(if $(filter test-spec,$(ORDERED_TEST_TARGETS)),test-spec-precheck)
|
||||
$(foreach test,$(ORDERED_TEST_TARGETS), \
|
||||
|
|
|
@ -178,6 +178,9 @@ Zachary Scott (zzak)
|
|||
|
||||
=== Libraries
|
||||
|
||||
[lib/bundler.rb, lib/bundler/*]
|
||||
Hiroshi SHIBATA (hsbt)
|
||||
https://github.com/bundler/bundler
|
||||
[lib/cmath.rb]
|
||||
_unmaintained_
|
||||
https://github.com/ruby/cmath
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
# coding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
begin
|
||||
require File.expand_path("../lib/bundler/version", __FILE__)
|
||||
rescue LoadError
|
||||
# for Ruby core repository
|
||||
require File.expand_path("../bundler/version", __FILE__)
|
||||
end
|
||||
|
||||
require "shellwords"
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "bundler"
|
||||
s.version = Bundler::VERSION
|
||||
s.license = "MIT"
|
||||
s.authors = [
|
||||
"André Arko", "Samuel Giddins", "Colby Swandale", "Hiroshi Shibata",
|
||||
"David Rodríguez", "Grey Baker", "Stephanie Morillo", "Chris Morris", "James Wen", "Tim Moore",
|
||||
"André Medeiros", "Jessica Lynn Suttles", "Terence Lee", "Carl Lerche",
|
||||
"Yehuda Katz"
|
||||
]
|
||||
s.email = ["team@bundler.io"]
|
||||
s.homepage = "http://bundler.io"
|
||||
s.summary = "The best way to manage your application's dependencies"
|
||||
s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably"
|
||||
|
||||
if s.respond_to?(:metadata=)
|
||||
s.metadata = {
|
||||
"bug_tracker_uri" => "http://github.com/bundler/bundler/issues",
|
||||
"changelog_uri" => "https://github.com/bundler/bundler/blob/master/CHANGELOG.md",
|
||||
"homepage_uri" => "https://bundler.io/",
|
||||
"source_code_uri" => "http://github.com/bundler/bundler/",
|
||||
}
|
||||
end
|
||||
|
||||
if s.version >= Gem::Version.new("2.a".dup)
|
||||
s.required_ruby_version = ">= 2.3.0"
|
||||
s.required_rubygems_version = ">= 2.5.0"
|
||||
else
|
||||
s.required_ruby_version = ">= 1.8.7"
|
||||
s.required_rubygems_version = ">= 1.3.6"
|
||||
end
|
||||
|
||||
s.add_development_dependency "automatiek", "~> 0.1.0"
|
||||
s.add_development_dependency "mustache", "0.99.6"
|
||||
s.add_development_dependency "rake", "~> 10.0"
|
||||
s.add_development_dependency "rdiscount", "~> 2.2"
|
||||
s.add_development_dependency "ronn", "~> 0.7.3"
|
||||
s.add_development_dependency "rspec", "~> 3.6"
|
||||
|
||||
s.files += %w[bundler.gemspec]
|
||||
|
||||
s.require_paths = ["lib"]
|
||||
end
|
|
@ -0,0 +1,567 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/compatibility_guard"
|
||||
|
||||
require "bundler/vendored_fileutils"
|
||||
require "pathname"
|
||||
require "rbconfig"
|
||||
require "thread"
|
||||
|
||||
require "bundler/errors"
|
||||
require "bundler/environment_preserver"
|
||||
require "bundler/plugin"
|
||||
require "bundler/rubygems_ext"
|
||||
require "bundler/rubygems_integration"
|
||||
require "bundler/version"
|
||||
require "bundler/constants"
|
||||
require "bundler/current_ruby"
|
||||
require "bundler/build_metadata"
|
||||
|
||||
module Bundler
|
||||
environment_preserver = EnvironmentPreserver.new(ENV, EnvironmentPreserver::BUNDLER_KEYS)
|
||||
ORIGINAL_ENV = environment_preserver.restore
|
||||
ENV.replace(environment_preserver.backup)
|
||||
SUDO_MUTEX = Mutex.new
|
||||
|
||||
autoload :Definition, "bundler/definition"
|
||||
autoload :Dependency, "bundler/dependency"
|
||||
autoload :DepProxy, "bundler/dep_proxy"
|
||||
autoload :Deprecate, "bundler/deprecate"
|
||||
autoload :Dsl, "bundler/dsl"
|
||||
autoload :EndpointSpecification, "bundler/endpoint_specification"
|
||||
autoload :Env, "bundler/env"
|
||||
autoload :Fetcher, "bundler/fetcher"
|
||||
autoload :FeatureFlag, "bundler/feature_flag"
|
||||
autoload :GemHelper, "bundler/gem_helper"
|
||||
autoload :GemHelpers, "bundler/gem_helpers"
|
||||
autoload :GemRemoteFetcher, "bundler/gem_remote_fetcher"
|
||||
autoload :GemVersionPromoter, "bundler/gem_version_promoter"
|
||||
autoload :Graph, "bundler/graph"
|
||||
autoload :Index, "bundler/index"
|
||||
autoload :Injector, "bundler/injector"
|
||||
autoload :Installer, "bundler/installer"
|
||||
autoload :LazySpecification, "bundler/lazy_specification"
|
||||
autoload :LockfileParser, "bundler/lockfile_parser"
|
||||
autoload :MatchPlatform, "bundler/match_platform"
|
||||
autoload :ProcessLock, "bundler/process_lock"
|
||||
autoload :RemoteSpecification, "bundler/remote_specification"
|
||||
autoload :Resolver, "bundler/resolver"
|
||||
autoload :Retry, "bundler/retry"
|
||||
autoload :RubyDsl, "bundler/ruby_dsl"
|
||||
autoload :RubyGemsGemInstaller, "bundler/rubygems_gem_installer"
|
||||
autoload :RubyVersion, "bundler/ruby_version"
|
||||
autoload :Runtime, "bundler/runtime"
|
||||
autoload :Settings, "bundler/settings"
|
||||
autoload :SharedHelpers, "bundler/shared_helpers"
|
||||
autoload :Source, "bundler/source"
|
||||
autoload :SourceList, "bundler/source_list"
|
||||
autoload :SpecSet, "bundler/spec_set"
|
||||
autoload :StubSpecification, "bundler/stub_specification"
|
||||
autoload :UI, "bundler/ui"
|
||||
autoload :URICredentialsFilter, "bundler/uri_credentials_filter"
|
||||
autoload :VersionRanges, "bundler/version_ranges"
|
||||
|
||||
class << self
|
||||
def configure
|
||||
@configured ||= configure_gem_home_and_path
|
||||
end
|
||||
|
||||
def ui
|
||||
(defined?(@ui) && @ui) || (self.ui = UI::Silent.new)
|
||||
end
|
||||
|
||||
def ui=(ui)
|
||||
Bundler.rubygems.ui = ui ? UI::RGProxy.new(ui) : nil
|
||||
@ui = ui
|
||||
end
|
||||
|
||||
# Returns absolute path of where gems are installed on the filesystem.
|
||||
def bundle_path
|
||||
@bundle_path ||= Pathname.new(configured_bundle_path.path).expand_path(root)
|
||||
end
|
||||
|
||||
def configured_bundle_path
|
||||
@configured_bundle_path ||= settings.path.tap(&:validate!)
|
||||
end
|
||||
|
||||
# Returns absolute location of where binstubs are installed to.
|
||||
def bin_path
|
||||
@bin_path ||= begin
|
||||
path = settings[:bin] || "bin"
|
||||
path = Pathname.new(path).expand_path(root).expand_path
|
||||
SharedHelpers.filesystem_access(path) {|p| FileUtils.mkdir_p(p) }
|
||||
path
|
||||
end
|
||||
end
|
||||
|
||||
def setup(*groups)
|
||||
# Return if all groups are already loaded
|
||||
return @setup if defined?(@setup) && @setup
|
||||
|
||||
definition.validate_runtime!
|
||||
|
||||
SharedHelpers.print_major_deprecations!
|
||||
|
||||
if groups.empty?
|
||||
# Load all groups, but only once
|
||||
@setup = load.setup
|
||||
else
|
||||
load.setup(*groups)
|
||||
end
|
||||
end
|
||||
|
||||
def require(*groups)
|
||||
setup(*groups).require(*groups)
|
||||
end
|
||||
|
||||
def load
|
||||
@load ||= Runtime.new(root, definition)
|
||||
end
|
||||
|
||||
def environment
|
||||
SharedHelpers.major_deprecation 2, "Bundler.environment has been removed in favor of Bundler.load"
|
||||
load
|
||||
end
|
||||
|
||||
# Returns an instance of Bundler::Definition for given Gemfile and lockfile
|
||||
#
|
||||
# @param unlock [Hash, Boolean, nil] Gems that have been requested
|
||||
# to be updated or true if all gems should be updated
|
||||
# @return [Bundler::Definition]
|
||||
def definition(unlock = nil)
|
||||
@definition = nil if unlock
|
||||
@definition ||= begin
|
||||
configure
|
||||
Definition.build(default_gemfile, default_lockfile, unlock)
|
||||
end
|
||||
end
|
||||
|
||||
def frozen_bundle?
|
||||
frozen = settings[:deployment]
|
||||
frozen ||= settings[:frozen] unless feature_flag.deployment_means_frozen?
|
||||
frozen
|
||||
end
|
||||
|
||||
def locked_gems
|
||||
@locked_gems ||=
|
||||
if defined?(@definition) && @definition
|
||||
definition.locked_gems
|
||||
elsif Bundler.default_lockfile.file?
|
||||
lock = Bundler.read_file(Bundler.default_lockfile)
|
||||
LockfileParser.new(lock)
|
||||
end
|
||||
end
|
||||
|
||||
def ruby_scope
|
||||
"#{Bundler.rubygems.ruby_engine}/#{Bundler.rubygems.config_map[:ruby_version]}"
|
||||
end
|
||||
|
||||
def user_home
|
||||
@user_home ||= begin
|
||||
home = Bundler.rubygems.user_home
|
||||
bundle_home = home ? File.join(home, ".bundle") : nil
|
||||
|
||||
warning = if home.nil?
|
||||
"Your home directory is not set."
|
||||
elsif !File.directory?(home)
|
||||
"`#{home}` is not a directory."
|
||||
elsif !File.writable?(home) && (!File.directory?(bundle_home) || !File.writable?(bundle_home))
|
||||
"`#{home}` is not writable."
|
||||
end
|
||||
|
||||
if warning
|
||||
Kernel.send(:require, "etc")
|
||||
user_home = tmp_home_path(Etc.getlogin, warning)
|
||||
Bundler.ui.warn "#{warning}\nBundler will use `#{user_home}' as your home directory temporarily.\n"
|
||||
user_home
|
||||
else
|
||||
Pathname.new(home)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def tmp_home_path(login, warning)
|
||||
login ||= "unknown"
|
||||
Kernel.send(:require, "tmpdir")
|
||||
path = Pathname.new(Dir.tmpdir).join("bundler", "home")
|
||||
SharedHelpers.filesystem_access(path) do |tmp_home_path|
|
||||
unless tmp_home_path.exist?
|
||||
tmp_home_path.mkpath
|
||||
tmp_home_path.chmod(0o777)
|
||||
end
|
||||
tmp_home_path.join(login).tap(&:mkpath)
|
||||
end
|
||||
rescue RuntimeError => e
|
||||
raise e.exception("#{warning}\nBundler also failed to create a temporary home directory at `#{path}':\n#{e}")
|
||||
end
|
||||
|
||||
def user_bundle_path(dir = "home")
|
||||
env_var, fallback = case dir
|
||||
when "home"
|
||||
["BUNDLE_USER_HOME", Pathname.new(user_home).join(".bundle")]
|
||||
when "cache"
|
||||
["BUNDLE_USER_CACHE", user_bundle_path.join("cache")]
|
||||
when "config"
|
||||
["BUNDLE_USER_CONFIG", user_bundle_path.join("config")]
|
||||
when "plugin"
|
||||
["BUNDLE_USER_PLUGIN", user_bundle_path.join("plugin")]
|
||||
else
|
||||
raise BundlerError, "Unknown user path requested: #{dir}"
|
||||
end
|
||||
# `fallback` will already be a Pathname, but Pathname.new() is
|
||||
# idempotent so it's OK
|
||||
Pathname.new(ENV.fetch(env_var, fallback))
|
||||
end
|
||||
|
||||
def user_cache
|
||||
user_bundle_path("cache")
|
||||
end
|
||||
|
||||
def home
|
||||
bundle_path.join("bundler")
|
||||
end
|
||||
|
||||
def install_path
|
||||
home.join("gems")
|
||||
end
|
||||
|
||||
def specs_path
|
||||
bundle_path.join("specifications")
|
||||
end
|
||||
|
||||
def root
|
||||
@root ||= begin
|
||||
SharedHelpers.root
|
||||
rescue GemfileNotFound
|
||||
bundle_dir = default_bundle_dir
|
||||
raise GemfileNotFound, "Could not locate Gemfile or .bundle/ directory" unless bundle_dir
|
||||
Pathname.new(File.expand_path("..", bundle_dir))
|
||||
end
|
||||
end
|
||||
|
||||
def app_config_path
|
||||
if app_config = ENV["BUNDLE_APP_CONFIG"]
|
||||
Pathname.new(app_config).expand_path(root)
|
||||
else
|
||||
root.join(".bundle")
|
||||
end
|
||||
end
|
||||
|
||||
def app_cache(custom_path = nil)
|
||||
path = custom_path || root
|
||||
Pathname.new(path).join(settings.app_cache_path)
|
||||
end
|
||||
|
||||
def tmp(name = Process.pid.to_s)
|
||||
Kernel.send(:require, "tmpdir")
|
||||
Pathname.new(Dir.mktmpdir(["bundler", name]))
|
||||
end
|
||||
|
||||
def rm_rf(path)
|
||||
FileUtils.remove_entry_secure(path) if path && File.exist?(path)
|
||||
rescue ArgumentError
|
||||
message = <<EOF
|
||||
It is a security vulnerability to allow your home directory to be world-writable, and bundler can not continue.
|
||||
You should probably consider fixing this issue by running `chmod o-w ~` on *nix.
|
||||
Please refer to http://ruby-doc.org/stdlib-2.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details.
|
||||
EOF
|
||||
File.world_writable?(path) ? Bundler.ui.warn(message) : raise
|
||||
raise PathError, "Please fix the world-writable issue with your #{path} directory"
|
||||
end
|
||||
|
||||
def settings
|
||||
@settings ||= Settings.new(app_config_path)
|
||||
rescue GemfileNotFound
|
||||
@settings = Settings.new(Pathname.new(".bundle").expand_path)
|
||||
end
|
||||
|
||||
# @return [Hash] Environment present before Bundler was activated
|
||||
def original_env
|
||||
ORIGINAL_ENV.clone
|
||||
end
|
||||
|
||||
# @deprecated Use `original_env` instead
|
||||
# @return [Hash] Environment with all bundler-related variables removed
|
||||
def clean_env
|
||||
Bundler::SharedHelpers.major_deprecation(2, "`Bundler.clean_env` has weird edge cases, use `.original_env` instead")
|
||||
env = original_env
|
||||
|
||||
if env.key?("BUNDLER_ORIG_MANPATH")
|
||||
env["MANPATH"] = env["BUNDLER_ORIG_MANPATH"]
|
||||
end
|
||||
|
||||
env.delete_if {|k, _| k[0, 7] == "BUNDLE_" }
|
||||
|
||||
if env.key?("RUBYOPT")
|
||||
env["RUBYOPT"] = env["RUBYOPT"].sub "-rbundler/setup", ""
|
||||
end
|
||||
|
||||
if env.key?("RUBYLIB")
|
||||
rubylib = env["RUBYLIB"].split(File::PATH_SEPARATOR)
|
||||
rubylib.delete(File.expand_path("..", __FILE__))
|
||||
env["RUBYLIB"] = rubylib.join(File::PATH_SEPARATOR)
|
||||
end
|
||||
|
||||
env
|
||||
end
|
||||
|
||||
def with_original_env
|
||||
with_env(original_env) { yield }
|
||||
end
|
||||
|
||||
def with_clean_env
|
||||
with_env(clean_env) { yield }
|
||||
end
|
||||
|
||||
def clean_system(*args)
|
||||
with_clean_env { Kernel.system(*args) }
|
||||
end
|
||||
|
||||
def clean_exec(*args)
|
||||
with_clean_env { Kernel.exec(*args) }
|
||||
end
|
||||
|
||||
def local_platform
|
||||
return Gem::Platform::RUBY if settings[:force_ruby_platform]
|
||||
Gem::Platform.local
|
||||
end
|
||||
|
||||
def default_gemfile
|
||||
SharedHelpers.default_gemfile
|
||||
end
|
||||
|
||||
def default_lockfile
|
||||
SharedHelpers.default_lockfile
|
||||
end
|
||||
|
||||
def default_bundle_dir
|
||||
SharedHelpers.default_bundle_dir
|
||||
end
|
||||
|
||||
def system_bindir
|
||||
# Gem.bindir doesn't always return the location that RubyGems will install
|
||||
# system binaries. If you put '-n foo' in your .gemrc, RubyGems will
|
||||
# install binstubs there instead. Unfortunately, RubyGems doesn't expose
|
||||
# that directory at all, so rather than parse .gemrc ourselves, we allow
|
||||
# the directory to be set as well, via `bundle config bindir foo`.
|
||||
Bundler.settings[:system_bindir] || Bundler.rubygems.gem_bindir
|
||||
end
|
||||
|
||||
def use_system_gems?
|
||||
configured_bundle_path.use_system_gems?
|
||||
end
|
||||
|
||||
def requires_sudo?
|
||||
return @requires_sudo if defined?(@requires_sudo_ran)
|
||||
|
||||
sudo_present = which "sudo" if settings.allow_sudo?
|
||||
|
||||
if sudo_present
|
||||
# the bundle path and subdirectories need to be writable for RubyGems
|
||||
# to be able to unpack and install gems without exploding
|
||||
path = bundle_path
|
||||
path = path.parent until path.exist?
|
||||
|
||||
# bins are written to a different location on OS X
|
||||
bin_dir = Pathname.new(Bundler.system_bindir)
|
||||
bin_dir = bin_dir.parent until bin_dir.exist?
|
||||
|
||||
# if any directory is not writable, we need sudo
|
||||
files = [path, bin_dir] | Dir[bundle_path.join("build_info/*").to_s] | Dir[bundle_path.join("*").to_s]
|
||||
unwritable_files = files.reject {|f| File.writable?(f) }
|
||||
sudo_needed = !unwritable_files.empty?
|
||||
if sudo_needed
|
||||
Bundler.ui.warn "Following files may not be writable, so sudo is needed:\n #{unwritable_files.map(&:to_s).sort.join("\n ")}"
|
||||
end
|
||||
end
|
||||
|
||||
@requires_sudo_ran = true
|
||||
@requires_sudo = settings.allow_sudo? && sudo_present && sudo_needed
|
||||
end
|
||||
|
||||
def mkdir_p(path, options = {})
|
||||
if requires_sudo? && !options[:no_sudo]
|
||||
sudo "mkdir -p '#{path}'" unless File.exist?(path)
|
||||
else
|
||||
SharedHelpers.filesystem_access(path, :write) do |p|
|
||||
FileUtils.mkdir_p(p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def which(executable)
|
||||
if File.file?(executable) && File.executable?(executable)
|
||||
executable
|
||||
elsif paths = ENV["PATH"]
|
||||
quote = '"'.freeze
|
||||
paths.split(File::PATH_SEPARATOR).find do |path|
|
||||
path = path[1..-2] if path.start_with?(quote) && path.end_with?(quote)
|
||||
executable_path = File.expand_path(executable, path)
|
||||
return executable_path if File.file?(executable_path) && File.executable?(executable_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sudo(str)
|
||||
SUDO_MUTEX.synchronize do
|
||||
prompt = "\n\n" + <<-PROMPT.gsub(/^ {6}/, "").strip + " "
|
||||
Your user account isn't allowed to install to the system RubyGems.
|
||||
You can cancel this installation and run:
|
||||
|
||||
bundle install --path vendor/bundle
|
||||
|
||||
to install the gems into ./vendor/bundle/, or you can enter your password
|
||||
and install the bundled gems to RubyGems using sudo.
|
||||
|
||||
Password:
|
||||
PROMPT
|
||||
|
||||
unless @prompted_for_sudo ||= system(%(sudo -k -p "#{prompt}" true))
|
||||
raise SudoNotPermittedError,
|
||||
"Bundler requires sudo access to install at the moment. " \
|
||||
"Try installing again, granting Bundler sudo access when prompted, or installing into a different path."
|
||||
end
|
||||
|
||||
`sudo -p "#{prompt}" #{str}`
|
||||
end
|
||||
end
|
||||
|
||||
def read_file(file)
|
||||
SharedHelpers.filesystem_access(file, :read) do
|
||||
File.open(file, "r:UTF-8", &:read)
|
||||
end
|
||||
end
|
||||
|
||||
def load_marshal(data)
|
||||
Marshal.load(data)
|
||||
rescue StandardError => e
|
||||
raise MarshalError, "#{e.class}: #{e.message}"
|
||||
end
|
||||
|
||||
def load_gemspec(file, validate = false)
|
||||
@gemspec_cache ||= {}
|
||||
key = File.expand_path(file)
|
||||
@gemspec_cache[key] ||= load_gemspec_uncached(file, validate)
|
||||
# Protect against caching side-effected gemspecs by returning a
|
||||
# new instance each time.
|
||||
@gemspec_cache[key].dup if @gemspec_cache[key]
|
||||
end
|
||||
|
||||
def load_gemspec_uncached(file, validate = false)
|
||||
path = Pathname.new(file)
|
||||
contents = read_file(file)
|
||||
spec = if contents.start_with?("---") # YAML header
|
||||
eval_yaml_gemspec(path, contents)
|
||||
else
|
||||
# Eval the gemspec from its parent directory, because some gemspecs
|
||||
# depend on "./" relative paths.
|
||||
SharedHelpers.chdir(path.dirname.to_s) do
|
||||
eval_gemspec(path, contents)
|
||||
end
|
||||
end
|
||||
return unless spec
|
||||
spec.loaded_from = path.expand_path.to_s
|
||||
Bundler.rubygems.validate(spec) if validate
|
||||
spec
|
||||
end
|
||||
|
||||
def clear_gemspec_cache
|
||||
@gemspec_cache = {}
|
||||
end
|
||||
|
||||
def git_present?
|
||||
return @git_present if defined?(@git_present)
|
||||
@git_present = Bundler.which("git") || Bundler.which("git.exe")
|
||||
end
|
||||
|
||||
def feature_flag
|
||||
@feature_flag ||= FeatureFlag.new(VERSION)
|
||||
end
|
||||
|
||||
def reset!
|
||||
reset_paths!
|
||||
Plugin.reset!
|
||||
reset_rubygems!
|
||||
end
|
||||
|
||||
def reset_paths!
|
||||
@bin_path = nil
|
||||
@bundler_major_version = nil
|
||||
@bundle_path = nil
|
||||
@configured = nil
|
||||
@configured_bundle_path = nil
|
||||
@definition = nil
|
||||
@load = nil
|
||||
@locked_gems = nil
|
||||
@root = nil
|
||||
@settings = nil
|
||||
@setup = nil
|
||||
@user_home = nil
|
||||
end
|
||||
|
||||
def reset_rubygems!
|
||||
return unless defined?(@rubygems) && @rubygems
|
||||
rubygems.undo_replacements
|
||||
rubygems.reset
|
||||
@rubygems = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def eval_yaml_gemspec(path, contents)
|
||||
Kernel.send(:require, "bundler/psyched_yaml")
|
||||
|
||||
# If the YAML is invalid, Syck raises an ArgumentError, and Psych
|
||||
# raises a Psych::SyntaxError. See psyched_yaml.rb for more info.
|
||||
Gem::Specification.from_yaml(contents)
|
||||
rescue YamlLibrarySyntaxError, ArgumentError, Gem::EndOfYAMLException, Gem::Exception
|
||||
eval_gemspec(path, contents)
|
||||
end
|
||||
|
||||
def eval_gemspec(path, contents)
|
||||
eval(contents, TOPLEVEL_BINDING.dup, path.expand_path.to_s)
|
||||
rescue ScriptError, StandardError => e
|
||||
msg = "There was an error while loading `#{path.basename}`: #{e.message}"
|
||||
|
||||
if e.is_a?(LoadError) && RUBY_VERSION >= "1.9"
|
||||
msg += "\nDoes it try to require a relative path? That's been removed in Ruby 1.9"
|
||||
end
|
||||
|
||||
raise GemspecError, Dsl::DSLError.new(msg, path, e.backtrace, contents)
|
||||
end
|
||||
|
||||
def configure_gem_home_and_path
|
||||
configure_gem_path
|
||||
configure_gem_home
|
||||
bundle_path
|
||||
end
|
||||
|
||||
def configure_gem_path(env = ENV)
|
||||
blank_home = env["GEM_HOME"].nil? || env["GEM_HOME"].empty?
|
||||
if !use_system_gems?
|
||||
# this needs to be empty string to cause
|
||||
# PathSupport.split_gem_path to only load up the
|
||||
# Bundler --path setting as the GEM_PATH.
|
||||
env["GEM_PATH"] = ""
|
||||
elsif blank_home
|
||||
possibles = [Bundler.rubygems.gem_dir, Bundler.rubygems.gem_path]
|
||||
paths = possibles.flatten.compact.uniq.reject(&:empty?)
|
||||
env["GEM_PATH"] = paths.join(File::PATH_SEPARATOR)
|
||||
end
|
||||
end
|
||||
|
||||
def configure_gem_home
|
||||
Bundler::SharedHelpers.set_env "GEM_HOME", File.expand_path(bundle_path, root)
|
||||
Bundler.rubygems.clear_paths
|
||||
end
|
||||
|
||||
# @param env [Hash]
|
||||
def with_env(env)
|
||||
backup = ENV.to_hash
|
||||
ENV.replace(env)
|
||||
yield
|
||||
ensure
|
||||
ENV.replace(backup)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# Represents metadata from when the Bundler gem was built.
|
||||
module BuildMetadata
|
||||
# begin ivars
|
||||
@release = false
|
||||
# end ivars
|
||||
|
||||
# A hash representation of the build metadata.
|
||||
def self.to_h
|
||||
{
|
||||
"Built At" => built_at,
|
||||
"Git SHA" => git_commit_sha,
|
||||
"Released Version" => release?,
|
||||
}
|
||||
end
|
||||
|
||||
# A string representing the date the bundler gem was built.
|
||||
def self.built_at
|
||||
@built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze
|
||||
end
|
||||
|
||||
# The SHA for the git commit the bundler gem was built from.
|
||||
def self.git_commit_sha
|
||||
return @git_commit_sha if @git_commit_sha
|
||||
|
||||
# If Bundler has been installed without its .git directory and without a
|
||||
# commit instance variable then we can't determine its commits SHA.
|
||||
git_dir = File.join(File.expand_path("../../..", __FILE__), ".git")
|
||||
return "unknown" unless File.directory?(git_dir)
|
||||
|
||||
# Otherwise shell out to git.
|
||||
@git_commit_sha = Dir.chdir(File.expand_path("..", __FILE__)) do
|
||||
`git rev-parse --short HEAD`.strip.freeze
|
||||
end
|
||||
end
|
||||
|
||||
# Whether this is an official release build of Bundler.
|
||||
def self.release?
|
||||
@release
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/shared_helpers"
|
||||
Bundler::SharedHelpers.major_deprecation 2,
|
||||
"The Bundler task for Capistrano. Please use http://github.com/capistrano/bundler"
|
||||
|
||||
# Capistrano task for Bundler.
|
||||
#
|
||||
# Add "require 'bundler/capistrano'" in your Capistrano deploy.rb, and
|
||||
# Bundler will be activated after each new deployment.
|
||||
require "bundler/deployment"
|
||||
require "capistrano/version"
|
||||
|
||||
if defined?(Capistrano::Version) && Gem::Version.new(Capistrano::Version).release >= Gem::Version.new("3.0")
|
||||
raise "For Capistrano 3.x integration, please use http://github.com/capistrano/bundler"
|
||||
end
|
||||
|
||||
Capistrano::Configuration.instance(:must_exist).load do
|
||||
before "deploy:finalize_update", "bundle:install"
|
||||
Bundler::Deployment.define_task(self, :task, :except => { :no_release => true })
|
||||
set :rake, lambda { "#{fetch(:bundle_cmd, "bundle")} exec rake" }
|
||||
end
|
|
@ -0,0 +1,790 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler"
|
||||
require "bundler/vendored_thor"
|
||||
|
||||
module Bundler
|
||||
class CLI < Thor
|
||||
require "bundler/cli/common"
|
||||
|
||||
package_name "Bundler"
|
||||
|
||||
AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze
|
||||
PARSEABLE_COMMANDS = %w[
|
||||
check config help exec platform show version
|
||||
].freeze
|
||||
|
||||
def self.start(*)
|
||||
super
|
||||
rescue Exception => e
|
||||
Bundler.ui = UI::Shell.new
|
||||
raise e
|
||||
ensure
|
||||
Bundler::SharedHelpers.print_major_deprecations!
|
||||
end
|
||||
|
||||
def self.dispatch(*)
|
||||
super do |i|
|
||||
i.send(:print_command)
|
||||
i.send(:warn_on_outdated_bundler)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile]
|
||||
if custom_gemfile && !custom_gemfile.empty?
|
||||
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
|
||||
Bundler.reset_paths!
|
||||
end
|
||||
|
||||
Bundler.settings.set_command_option_if_given :retry, options[:retry]
|
||||
|
||||
current_cmd = args.last[:current_command].name
|
||||
auto_install if AUTO_INSTALL_CMDS.include?(current_cmd)
|
||||
rescue UnknownArgumentError => e
|
||||
raise InvalidOption, e.message
|
||||
ensure
|
||||
self.options ||= {}
|
||||
unprinted_warnings = Bundler.ui.unprinted_warnings
|
||||
Bundler.ui = UI::Shell.new(options)
|
||||
Bundler.ui.level = "debug" if options["verbose"]
|
||||
unprinted_warnings.each {|w| Bundler.ui.warn(w) }
|
||||
|
||||
if ENV["RUBYGEMS_GEMDEPS"] && !ENV["RUBYGEMS_GEMDEPS"].empty?
|
||||
Bundler.ui.warn(
|
||||
"The RUBYGEMS_GEMDEPS environment variable is set. This enables RubyGems' " \
|
||||
"experimental Gemfile mode, which may conflict with Bundler and cause unexpected errors. " \
|
||||
"To remove this warning, unset RUBYGEMS_GEMDEPS.", :wrap => true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def self.deprecated_option(*args, &blk)
|
||||
return if Bundler.feature_flag.forget_cli_options?
|
||||
method_option(*args, &blk)
|
||||
end
|
||||
|
||||
check_unknown_options!(:except => [:config, :exec])
|
||||
stop_on_unknown_option! :exec
|
||||
|
||||
desc "cli_help", "Prints a summary of bundler commands", :hide => true
|
||||
def cli_help
|
||||
version
|
||||
Bundler.ui.info "\n"
|
||||
|
||||
primary_commands = ["install", "update",
|
||||
Bundler.feature_flag.cache_command_is_package? ? "cache" : "package",
|
||||
"exec", "config", "help"]
|
||||
|
||||
list = self.class.printable_commands(true)
|
||||
by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] }
|
||||
utilities = by_name.keys.sort - primary_commands
|
||||
primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first }
|
||||
utilities.map! {|name| by_name[name].first }
|
||||
|
||||
shell.say "Bundler commands:\n\n"
|
||||
|
||||
shell.say " Primary commands:\n"
|
||||
shell.print_table(primary_commands, :indent => 4, :truncate => true)
|
||||
shell.say
|
||||
shell.say " Utilities:\n"
|
||||
shell.print_table(utilities, :indent => 4, :truncate => true)
|
||||
shell.say
|
||||
self.class.send(:class_options_help, shell)
|
||||
end
|
||||
default_task(Bundler.feature_flag.default_cli_command)
|
||||
|
||||
class_option "no-color", :type => :boolean, :desc => "Disable colorization in output"
|
||||
class_option "retry", :type => :numeric, :aliases => "-r", :banner => "NUM",
|
||||
:desc => "Specify the number of times you wish to attempt network commands"
|
||||
class_option "verbose", :type => :boolean, :desc => "Enable verbose output mode", :aliases => "-V"
|
||||
|
||||
def help(cli = nil)
|
||||
case cli
|
||||
when "gemfile" then command = "gemfile"
|
||||
when nil then command = "bundle"
|
||||
else command = "bundle-#{cli}"
|
||||
end
|
||||
|
||||
man_path = File.expand_path("../../../man", __FILE__)
|
||||
man_pages = Hash[Dir.glob(File.join(man_path, "*")).grep(/.*\.\d*\Z/).collect do |f|
|
||||
[File.basename(f, ".*"), f]
|
||||
end]
|
||||
|
||||
if man_pages.include?(command)
|
||||
if Bundler.which("man") && man_path !~ %r{^file:/.+!/META-INF/jruby.home/.+}
|
||||
Kernel.exec "man #{man_pages[command]}"
|
||||
else
|
||||
puts File.read("#{man_path}/#{File.basename(man_pages[command])}.txt")
|
||||
end
|
||||
elsif command_path = Bundler.which("bundler-#{cli}")
|
||||
Kernel.exec(command_path, "--help")
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def self.handle_no_command_error(command, has_namespace = $thor_runner)
|
||||
if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command)
|
||||
return Bundler::Plugin.exec_command(command, ARGV[1..-1])
|
||||
end
|
||||
|
||||
return super unless command_path = Bundler.which("bundler-#{command}")
|
||||
|
||||
Kernel.exec(command_path, *ARGV[1..-1])
|
||||
end
|
||||
|
||||
desc "init [OPTIONS]", "Generates a Gemfile into the current working directory"
|
||||
long_desc <<-D
|
||||
Init generates a default Gemfile in the current working directory. When adding a
|
||||
Gemfile to a gem with a gemspec, the --gemspec option will automatically add each
|
||||
dependency listed in the gemspec file to the newly created Gemfile.
|
||||
D
|
||||
deprecated_option "gemspec", :type => :string, :banner => "Use the specified .gemspec to create the Gemfile"
|
||||
def init
|
||||
require "bundler/cli/init"
|
||||
Init.new(options.dup).run
|
||||
end
|
||||
|
||||
desc "check [OPTIONS]", "Checks if the dependencies listed in Gemfile are satisfied by currently installed gems"
|
||||
long_desc <<-D
|
||||
Check searches the local machine for each of the gems requested in the Gemfile. If
|
||||
all gems are found, Bundler prints a success message and exits with a status of 0.
|
||||
If not, the first missing gem is listed and Bundler exits status 1.
|
||||
D
|
||||
method_option "dry-run", :type => :boolean, :default => false, :banner =>
|
||||
"Lock the Gemfile"
|
||||
method_option "gemfile", :type => :string, :banner =>
|
||||
"Use the specified gemfile instead of Gemfile"
|
||||
method_option "path", :type => :string, :banner =>
|
||||
"Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}"
|
||||
map "c" => "check"
|
||||
def check
|
||||
require "bundler/cli/check"
|
||||
Check.new(options).run
|
||||
end
|
||||
|
||||
desc "remove [GEM [GEM ...]]", "Removes gems from the Gemfile"
|
||||
long_desc <<-D
|
||||
Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If the gem is not found, Bundler prints a error message and if gem could not be removed due to any reason Bundler will display a warning.
|
||||
D
|
||||
method_option "install", :type => :boolean, :banner =>
|
||||
"Runs 'bundle install' after removing the gems from the Gemfile"
|
||||
def remove(*gems)
|
||||
require "bundler/cli/remove"
|
||||
Remove.new(gems, options).run
|
||||
end
|
||||
|
||||
desc "install [OPTIONS]", "Install the current environment to the system"
|
||||
long_desc <<-D
|
||||
Install will install all of the gems in the current bundle, making them available
|
||||
for use. In a freshly checked out repository, this command will give you the same
|
||||
gem versions as the last person who updated the Gemfile and ran `bundle update`.
|
||||
|
||||
Passing [DIR] to install (e.g. vendor) will cause the unpacked gems to be installed
|
||||
into the [DIR] directory rather than into system gems.
|
||||
|
||||
If the bundle has already been installed, bundler will tell you so and then exit.
|
||||
D
|
||||
deprecated_option "binstubs", :type => :string, :lazy_default => "bin", :banner =>
|
||||
"Generate bin stubs for bundled gems to ./bin"
|
||||
deprecated_option "clean", :type => :boolean, :banner =>
|
||||
"Run bundle clean automatically after install"
|
||||
deprecated_option "deployment", :type => :boolean, :banner =>
|
||||
"Install using defaults tuned for deployment environments"
|
||||
deprecated_option "frozen", :type => :boolean, :banner =>
|
||||
"Do not allow the Gemfile.lock to be updated after this install"
|
||||
method_option "full-index", :type => :boolean, :banner =>
|
||||
"Fall back to using the single-file index of all gems"
|
||||
method_option "gemfile", :type => :string, :banner =>
|
||||
"Use the specified gemfile instead of Gemfile"
|
||||
method_option "jobs", :aliases => "-j", :type => :numeric, :banner =>
|
||||
"Specify the number of jobs to run in parallel"
|
||||
method_option "local", :type => :boolean, :banner =>
|
||||
"Do not attempt to fetch gems remotely and use the gem cache instead"
|
||||
deprecated_option "no-cache", :type => :boolean, :banner =>
|
||||
"Don't update the existing gem cache."
|
||||
method_option "redownload", :type => :boolean, :aliases => "--force", :banner =>
|
||||
"Force downloading every gem."
|
||||
deprecated_option "no-prune", :type => :boolean, :banner =>
|
||||
"Don't remove stale gems from the cache."
|
||||
deprecated_option "path", :type => :string, :banner =>
|
||||
"Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine"
|
||||
method_option "quiet", :type => :boolean, :banner =>
|
||||
"Only output warnings and errors."
|
||||
deprecated_option "shebang", :type => :string, :banner =>
|
||||
"Specify a different shebang executable name than the default (usually 'ruby')"
|
||||
method_option "standalone", :type => :array, :lazy_default => [], :banner =>
|
||||
"Make a bundle that can work without the Bundler runtime"
|
||||
deprecated_option "system", :type => :boolean, :banner =>
|
||||
"Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application"
|
||||
method_option "trust-policy", :alias => "P", :type => :string, :banner =>
|
||||
"Gem trust policy (like gem install -P). Must be one of " +
|
||||
Bundler.rubygems.security_policy_keys.join("|")
|
||||
deprecated_option "without", :type => :array, :banner =>
|
||||
"Exclude gems that are part of the specified named group."
|
||||
deprecated_option "with", :type => :array, :banner =>
|
||||
"Include gems that are part of the specified named group."
|
||||
map "i" => "install"
|
||||
def install
|
||||
SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force")
|
||||
require "bundler/cli/install"
|
||||
Bundler.settings.temporary(:no_install => false) do
|
||||
Install.new(options.dup).run
|
||||
end
|
||||
end
|
||||
|
||||
desc "update [OPTIONS]", "Update the current environment"
|
||||
long_desc <<-D
|
||||
Update will install the newest versions of the gems listed in the Gemfile. Use
|
||||
update when you have changed the Gemfile, or if you want to get the newest
|
||||
possible versions of the gems in the bundle.
|
||||
D
|
||||
method_option "full-index", :type => :boolean, :banner =>
|
||||
"Fall back to using the single-file index of all gems"
|
||||
method_option "gemfile", :type => :string, :banner =>
|
||||
"Use the specified gemfile instead of Gemfile"
|
||||
method_option "group", :aliases => "-g", :type => :array, :banner =>
|
||||
"Update a specific group"
|
||||
method_option "jobs", :aliases => "-j", :type => :numeric, :banner =>
|
||||
"Specify the number of jobs to run in parallel"
|
||||
method_option "local", :type => :boolean, :banner =>
|
||||
"Do not attempt to fetch gems remotely and use the gem cache instead"
|
||||
method_option "quiet", :type => :boolean, :banner =>
|
||||
"Only output warnings and errors."
|
||||
method_option "source", :type => :array, :banner =>
|
||||
"Update a specific source (and all gems associated with it)"
|
||||
method_option "redownload", :type => :boolean, :aliases => "--force", :banner =>
|
||||
"Force downloading every gem."
|
||||
method_option "ruby", :type => :boolean, :banner =>
|
||||
"Update ruby specified in Gemfile.lock"
|
||||
method_option "bundler", :type => :string, :lazy_default => "> 0.a", :banner =>
|
||||
"Update the locked version of bundler"
|
||||
method_option "patch", :type => :boolean, :banner =>
|
||||
"Prefer updating only to next patch version"
|
||||
method_option "minor", :type => :boolean, :banner =>
|
||||
"Prefer updating only to next minor version"
|
||||
method_option "major", :type => :boolean, :banner =>
|
||||
"Prefer updating to next major version (default)"
|
||||
method_option "strict", :type => :boolean, :banner =>
|
||||
"Do not allow any gem to be updated past latest --patch | --minor | --major"
|
||||
method_option "conservative", :type => :boolean, :banner =>
|
||||
"Use bundle install conservative update behavior and do not allow shared dependencies to be updated."
|
||||
method_option "all", :type => :boolean, :banner =>
|
||||
"Update everything."
|
||||
def update(*gems)
|
||||
SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force")
|
||||
require "bundler/cli/update"
|
||||
Update.new(options, gems).run
|
||||
end
|
||||
|
||||
desc "show GEM [OPTIONS]", "Shows all gems that are part of the bundle, or the path to a given gem"
|
||||
long_desc <<-D
|
||||
Show lists the names and versions of all gems that are required by your Gemfile.
|
||||
Calling show with [GEM] will list the exact location of that gem on your machine.
|
||||
D
|
||||
method_option "paths", :type => :boolean,
|
||||
:banner => "List the paths of all gems that are required by your Gemfile."
|
||||
method_option "outdated", :type => :boolean,
|
||||
:banner => "Show verbose output including whether gems are outdated."
|
||||
def show(gem_name = nil)
|
||||
if ARGV[0] == "show"
|
||||
rest = ARGV[1..-1]
|
||||
|
||||
new_command = rest.find {|arg| !arg.start_with?("--") } ? "info" : "list"
|
||||
|
||||
new_arguments = rest.map do |arg|
|
||||
next arg if arg != "--paths"
|
||||
next "--path" if new_command == "info"
|
||||
end
|
||||
|
||||
old_argv = ARGV.join(" ")
|
||||
new_argv = [new_command, *new_arguments.compact].join(" ")
|
||||
|
||||
Bundler::SharedHelpers.major_deprecation(2, "use `bundle #{new_argv}` instead of `bundle #{old_argv}`")
|
||||
end
|
||||
require "bundler/cli/show"
|
||||
Show.new(options, gem_name).run
|
||||
end
|
||||
# TODO: 2.0 remove `bundle show`
|
||||
|
||||
if Bundler.feature_flag.list_command?
|
||||
desc "list", "List all gems in the bundle"
|
||||
method_option "name-only", :type => :boolean, :banner => "print only the gem names"
|
||||
method_option "only-group", :type => :string, :banner => "print gems from a particular group"
|
||||
method_option "without-group", :type => :string, :banner => "print all gems expect from a group"
|
||||
method_option "paths", :type => :boolean, :banner => "print the path to each gem in the bundle"
|
||||
def list
|
||||
require "bundler/cli/list"
|
||||
List.new(options).run
|
||||
end
|
||||
|
||||
map %w[ls] => "list"
|
||||
else
|
||||
map %w[list] => "show"
|
||||
end
|
||||
|
||||
desc "info GEM [OPTIONS]", "Show information for the given gem"
|
||||
method_option "path", :type => :boolean, :banner => "Print full path to gem"
|
||||
def info(gem_name)
|
||||
require "bundler/cli/info"
|
||||
Info.new(options, gem_name).run
|
||||
end
|
||||
|
||||
desc "binstubs GEM [OPTIONS]", "Install the binstubs of the listed gem"
|
||||
long_desc <<-D
|
||||
Generate binstubs for executables in [GEM]. Binstubs are put into bin,
|
||||
or the --binstubs directory if one has been set. Calling binstubs with [GEM [GEM]]
|
||||
will create binstubs for all given gems.
|
||||
D
|
||||
method_option "force", :type => :boolean, :default => false, :banner =>
|
||||
"Overwrite existing binstubs if they exist"
|
||||
method_option "path", :type => :string, :lazy_default => "bin", :banner =>
|
||||
"Binstub destination directory (default bin)"
|
||||
method_option "shebang", :type => :string, :banner =>
|
||||
"Specify a different shebang executable name than the default (usually 'ruby')"
|
||||
method_option "standalone", :type => :boolean, :banner =>
|
||||
"Make binstubs that can work without the Bundler runtime"
|
||||
method_option "all", :type => :boolean, :banner =>
|
||||
"Install binstubs for all gems"
|
||||
def binstubs(*gems)
|
||||
require "bundler/cli/binstubs"
|
||||
Binstubs.new(options, gems).run
|
||||
end
|
||||
|
||||
desc "add GEM VERSION", "Add gem to Gemfile and run bundle install"
|
||||
long_desc <<-D
|
||||
Adds the specified gem to Gemfile (if valid) and run 'bundle install' in one step.
|
||||
D
|
||||
method_option "version", :aliases => "-v", :type => :string
|
||||
method_option "group", :aliases => "-g", :type => :string
|
||||
method_option "source", :aliases => "-s", :type => :string
|
||||
method_option "skip-install", :type => :boolean, :banner =>
|
||||
"Adds gem to the Gemfile but does not install it"
|
||||
method_option "optimistic", :type => :boolean, :banner => "Adds optimistic declaration of version to gem"
|
||||
method_option "strict", :type => :boolean, :banner => "Adds strict declaration of version to gem"
|
||||
def add(*gems)
|
||||
require "bundler/cli/add"
|
||||
Add.new(options.dup, gems).run
|
||||
end
|
||||
|
||||
desc "outdated GEM [OPTIONS]", "List installed gems with newer versions available"
|
||||
long_desc <<-D
|
||||
Outdated lists the names and versions of gems that have a newer version available
|
||||
in the given source. Calling outdated with [GEM [GEM]] will only check for newer
|
||||
versions of the given gems. Prerelease gems are ignored by default. If your gems
|
||||
are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1.
|
||||
|
||||
For more information on patch level options (--major, --minor, --patch,
|
||||
--update-strict) see documentation on the same options on the update command.
|
||||
D
|
||||
method_option "group", :type => :string, :banner => "List gems from a specific group"
|
||||
method_option "groups", :type => :boolean, :banner => "List gems organized by groups"
|
||||
method_option "local", :type => :boolean, :banner =>
|
||||
"Do not attempt to fetch gems remotely and use the gem cache instead"
|
||||
method_option "pre", :type => :boolean, :banner => "Check for newer pre-release gems"
|
||||
method_option "source", :type => :array, :banner => "Check against a specific source"
|
||||
method_option "strict", :type => :boolean, :banner =>
|
||||
"Only list newer versions allowed by your Gemfile requirements"
|
||||
method_option "update-strict", :type => :boolean, :banner =>
|
||||
"Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor | --major"
|
||||
method_option "minor", :type => :boolean, :banner => "Prefer updating only to next minor version"
|
||||
method_option "major", :type => :boolean, :banner => "Prefer updating to next major version (default)"
|
||||
method_option "patch", :type => :boolean, :banner => "Prefer updating only to next patch version"
|
||||
method_option "filter-major", :type => :boolean, :banner => "Only list major newer versions"
|
||||
method_option "filter-minor", :type => :boolean, :banner => "Only list minor newer versions"
|
||||
method_option "filter-patch", :type => :boolean, :banner => "Only list patch newer versions"
|
||||
method_option "parseable", :aliases => "--porcelain", :type => :boolean, :banner =>
|
||||
"Use minimal formatting for more parseable output"
|
||||
method_option "only-explicit", :type => :boolean, :banner =>
|
||||
"Only list gems specified in your Gemfile, not their dependencies"
|
||||
def outdated(*gems)
|
||||
require "bundler/cli/outdated"
|
||||
Outdated.new(options, gems).run
|
||||
end
|
||||
|
||||
if Bundler.feature_flag.cache_command_is_package?
|
||||
map %w[cache] => :package
|
||||
else
|
||||
desc "cache [OPTIONS]", "Cache all the gems to vendor/cache", :hide => true
|
||||
unless Bundler.feature_flag.cache_command_is_package?
|
||||
method_option "all", :type => :boolean,
|
||||
:banner => "Include all sources (including path and git)."
|
||||
end
|
||||
method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms present in the lockfile, not only the current one"
|
||||
method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache."
|
||||
def cache
|
||||
require "bundler/cli/cache"
|
||||
Cache.new(options).run
|
||||
end
|
||||
end
|
||||
|
||||
desc "#{Bundler.feature_flag.cache_command_is_package? ? :cache : :package} [OPTIONS]", "Locks and then caches all of the gems into vendor/cache"
|
||||
unless Bundler.feature_flag.cache_command_is_package?
|
||||
method_option "all", :type => :boolean,
|
||||
:banner => "Include all sources (including path and git)."
|
||||
end
|
||||
method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms present in the lockfile, not only the current one"
|
||||
method_option "cache-path", :type => :string, :banner =>
|
||||
"Specify a different cache path than the default (vendor/cache)."
|
||||
method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile"
|
||||
method_option "no-install", :type => :boolean, :banner => "Don't install the gems, only the package."
|
||||
method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache."
|
||||
method_option "path", :type => :string, :banner =>
|
||||
"Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine"
|
||||
method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors."
|
||||
method_option "frozen", :type => :boolean, :banner =>
|
||||
"Do not allow the Gemfile.lock to be updated after this package operation's install"
|
||||
long_desc <<-D
|
||||
The package command will copy the .gem files for every gem in the bundle into the
|
||||
directory ./vendor/cache. If you then check that directory into your source
|
||||
control repository, others who check out your source will be able to install the
|
||||
bundle without having to download any additional gems.
|
||||
D
|
||||
def package
|
||||
require "bundler/cli/package"
|
||||
Package.new(options).run
|
||||
end
|
||||
map %w[pack] => :package
|
||||
|
||||
desc "exec [OPTIONS]", "Run the command in context of the bundle"
|
||||
method_option :keep_file_descriptors, :type => :boolean, :default => false
|
||||
method_option :gemfile, :type => :string, :required => false
|
||||
long_desc <<-D
|
||||
Exec runs a command, providing it access to the gems in the bundle. While using
|
||||
bundle exec you can require and call the bundled gems as if they were installed
|
||||
into the system wide RubyGems repository.
|
||||
D
|
||||
map "e" => "exec"
|
||||
def exec(*args)
|
||||
require "bundler/cli/exec"
|
||||
Exec.new(options, args).run
|
||||
end
|
||||
|
||||
desc "config NAME [VALUE]", "Retrieve or set a configuration value"
|
||||
long_desc <<-D
|
||||
Retrieves or sets a configuration value. If only one parameter is provided, retrieve the value. If two parameters are provided, replace the
|
||||
existing value with the newly provided one.
|
||||
|
||||
By default, setting a configuration value sets it for all projects
|
||||
on the machine.
|
||||
|
||||
If a global setting is superceded by local configuration, this command
|
||||
will show the current value, as well as any superceded values and
|
||||
where they were specified.
|
||||
D
|
||||
method_option "parseable", :type => :boolean, :banner => "Use minimal formatting for more parseable output"
|
||||
def config(*args)
|
||||
require "bundler/cli/config"
|
||||
Config.new(options, args, self).run
|
||||
end
|
||||
|
||||
desc "open GEM", "Opens the source directory of the given bundled gem"
|
||||
def open(name)
|
||||
require "bundler/cli/open"
|
||||
Open.new(options, name).run
|
||||
end
|
||||
|
||||
if Bundler.feature_flag.console_command?
|
||||
desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded"
|
||||
def console(group = nil)
|
||||
require "bundler/cli/console"
|
||||
Console.new(options, group).run
|
||||
end
|
||||
end
|
||||
|
||||
desc "version", "Prints the bundler's version information"
|
||||
def version
|
||||
cli_help = current_command.name == "cli_help"
|
||||
if cli_help || ARGV.include?("version")
|
||||
build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})"
|
||||
end
|
||||
|
||||
if !cli_help && Bundler.feature_flag.print_only_version_number?
|
||||
Bundler.ui.info "#{Bundler::VERSION}#{build_info}"
|
||||
else
|
||||
Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}"
|
||||
end
|
||||
end
|
||||
map %w[-v --version] => :version
|
||||
|
||||
desc "licenses", "Prints the license of all gems in the bundle"
|
||||
def licenses
|
||||
Bundler.load.specs.sort_by {|s| s.license.to_s }.reverse_each do |s|
|
||||
gem_name = s.name
|
||||
license = s.license || s.licenses
|
||||
|
||||
if license.empty?
|
||||
Bundler.ui.warn "#{gem_name}: Unknown"
|
||||
else
|
||||
Bundler.ui.info "#{gem_name}: #{license}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.feature_flag.viz_command?
|
||||
desc "viz [OPTIONS]", "Generates a visual dependency graph", :hide => true
|
||||
long_desc <<-D
|
||||
Viz generates a PNG file of the current Gemfile as a dependency graph.
|
||||
Viz requires the ruby-graphviz gem (and its dependencies).
|
||||
The associated gems must also be installed via 'bundle install'.
|
||||
D
|
||||
method_option :file, :type => :string, :default => "gem_graph", :aliases => "-f", :desc => "The name to use for the generated file. see format option"
|
||||
method_option :format, :type => :string, :default => "png", :aliases => "-F", :desc => "This is output format option. Supported format is png, jpg, svg, dot ..."
|
||||
method_option :requirements, :type => :boolean, :default => false, :aliases => "-R", :desc => "Set to show the version of each required dependency."
|
||||
method_option :version, :type => :boolean, :default => false, :aliases => "-v", :desc => "Set to show each gem version."
|
||||
method_option :without, :type => :array, :default => [], :aliases => "-W", :banner => "GROUP[ GROUP...]", :desc => "Exclude gems that are part of the specified named group."
|
||||
def viz
|
||||
SharedHelpers.major_deprecation 2, "The `viz` command has been moved to the `bundle-viz` gem, see https://github.com/bundler/bundler-viz"
|
||||
require "bundler/cli/viz"
|
||||
Viz.new(options.dup).run
|
||||
end
|
||||
end
|
||||
|
||||
old_gem = instance_method(:gem)
|
||||
|
||||
desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem"
|
||||
method_option :exe, :type => :boolean, :default => false, :aliases => ["--bin", "-b"], :desc => "Generate a binary executable for your library."
|
||||
method_option :coc, :type => :boolean, :desc => "Generate a code of conduct file. Set a default with `bundle config gem.coc true`."
|
||||
method_option :edit, :type => :string, :aliases => "-e", :required => false, :banner => "EDITOR",
|
||||
:lazy_default => [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? },
|
||||
:desc => "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)"
|
||||
method_option :ext, :type => :boolean, :default => false, :desc => "Generate the boilerplate for C extension code"
|
||||
method_option :mit, :type => :boolean, :desc => "Generate an MIT license file. Set a default with `bundle config gem.mit true`."
|
||||
method_option :test, :type => :string, :lazy_default => "rspec", :aliases => "-t", :banner => "rspec",
|
||||
:desc => "Generate a test directory for your library, either rspec or minitest. Set a default with `bundle config gem.test rspec`."
|
||||
def gem(name)
|
||||
end
|
||||
|
||||
commands["gem"].tap do |gem_command|
|
||||
def gem_command.run(instance, args = [])
|
||||
arity = 1 # name
|
||||
|
||||
require "bundler/cli/gem"
|
||||
cmd_args = args + [instance]
|
||||
cmd_args.unshift(instance.options)
|
||||
|
||||
cmd = begin
|
||||
Gem.new(*cmd_args)
|
||||
rescue ArgumentError => e
|
||||
instance.class.handle_argument_error(self, e, args, arity)
|
||||
end
|
||||
|
||||
cmd.run
|
||||
end
|
||||
end
|
||||
|
||||
undef_method(:gem)
|
||||
define_method(:gem, old_gem)
|
||||
private :gem
|
||||
|
||||
def self.source_root
|
||||
File.expand_path(File.join(File.dirname(__FILE__), "templates"))
|
||||
end
|
||||
|
||||
desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory", :hide => true
|
||||
method_option "dry-run", :type => :boolean, :default => false, :banner =>
|
||||
"Only print out changes, do not clean gems"
|
||||
method_option "force", :type => :boolean, :default => false, :banner =>
|
||||
"Forces clean even if --path is not set"
|
||||
def clean
|
||||
require "bundler/cli/clean"
|
||||
Clean.new(options.dup).run
|
||||
end
|
||||
|
||||
desc "platform [OPTIONS]", "Displays platform compatibility information"
|
||||
method_option "ruby", :type => :boolean, :default => false, :banner =>
|
||||
"only display ruby related platform information"
|
||||
def platform
|
||||
require "bundler/cli/platform"
|
||||
Platform.new(options).run
|
||||
end
|
||||
|
||||
desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", :hide => true
|
||||
method_option "source", :type => :string, :banner =>
|
||||
"Install gem from the given source"
|
||||
method_option "group", :type => :string, :banner =>
|
||||
"Install gem into a bundler group"
|
||||
def inject(name, version)
|
||||
SharedHelpers.major_deprecation 2, "The `inject` command has been replaced by the `add` command"
|
||||
require "bundler/cli/inject"
|
||||
Inject.new(options.dup, name, version).run
|
||||
end
|
||||
|
||||
desc "lock", "Creates a lockfile without installing"
|
||||
method_option "update", :type => :array, :lazy_default => true, :banner =>
|
||||
"ignore the existing lockfile, update all gems by default, or update list of given gems"
|
||||
method_option "local", :type => :boolean, :default => false, :banner =>
|
||||
"do not attempt to fetch remote gemspecs and use the local gem cache only"
|
||||
method_option "print", :type => :boolean, :default => false, :banner =>
|
||||
"print the lockfile to STDOUT instead of writing to the file system"
|
||||
method_option "lockfile", :type => :string, :default => nil, :banner =>
|
||||
"the path the lockfile should be written to"
|
||||
method_option "full-index", :type => :boolean, :default => false, :banner =>
|
||||
"Fall back to using the single-file index of all gems"
|
||||
method_option "add-platform", :type => :array, :default => [], :banner =>
|
||||
"Add a new platform to the lockfile"
|
||||
method_option "remove-platform", :type => :array, :default => [], :banner =>
|
||||
"Remove a platform from the lockfile"
|
||||
method_option "patch", :type => :boolean, :banner =>
|
||||
"If updating, prefer updating only to next patch version"
|
||||
method_option "minor", :type => :boolean, :banner =>
|
||||
"If updating, prefer updating only to next minor version"
|
||||
method_option "major", :type => :boolean, :banner =>
|
||||
"If updating, prefer updating to next major version (default)"
|
||||
method_option "strict", :type => :boolean, :banner =>
|
||||
"If updating, do not allow any gem to be updated past latest --patch | --minor | --major"
|
||||
method_option "conservative", :type => :boolean, :banner =>
|
||||
"If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated"
|
||||
def lock
|
||||
require "bundler/cli/lock"
|
||||
Lock.new(options).run
|
||||
end
|
||||
|
||||
desc "env", "Print information about the environment Bundler is running under"
|
||||
def env
|
||||
Env.write($stdout)
|
||||
end
|
||||
|
||||
desc "doctor [OPTIONS]", "Checks the bundle for common problems"
|
||||
long_desc <<-D
|
||||
Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If
|
||||
missing dependencies are detected, Bundler prints them and exits status 1.
|
||||
Otherwise, Bundler prints a success message and exits with a status of 0.
|
||||
D
|
||||
method_option "gemfile", :type => :string, :banner =>
|
||||
"Use the specified gemfile instead of Gemfile"
|
||||
method_option "quiet", :type => :boolean, :banner =>
|
||||
"Only output warnings and errors."
|
||||
def doctor
|
||||
require "bundler/cli/doctor"
|
||||
Doctor.new(options).run
|
||||
end
|
||||
|
||||
desc "issue", "Learn how to report an issue in Bundler"
|
||||
def issue
|
||||
require "bundler/cli/issue"
|
||||
Issue.new.run
|
||||
end
|
||||
|
||||
desc "pristine [GEMS...]", "Restores installed gems to pristine condition"
|
||||
long_desc <<-D
|
||||
Restores installed gems to pristine condition from files located in the
|
||||
gem cache. Gems installed from a git repository will be issued `git
|
||||
checkout --force`.
|
||||
D
|
||||
def pristine(*gems)
|
||||
require "bundler/cli/pristine"
|
||||
Pristine.new(gems).run
|
||||
end
|
||||
|
||||
if Bundler.feature_flag.plugins?
|
||||
require "bundler/cli/plugin"
|
||||
desc "plugin", "Manage the bundler plugins"
|
||||
subcommand "plugin", Plugin
|
||||
end
|
||||
|
||||
# Reformat the arguments passed to bundle that include a --help flag
|
||||
# into the corresponding `bundle help #{command}` call
|
||||
def self.reformatted_help_args(args)
|
||||
bundler_commands = all_commands.keys
|
||||
help_flags = %w[--help -h]
|
||||
exec_commands = %w[e ex exe exec]
|
||||
help_used = args.index {|a| help_flags.include? a }
|
||||
exec_used = args.index {|a| exec_commands.include? a }
|
||||
command = args.find {|a| bundler_commands.include? a }
|
||||
if exec_used && help_used
|
||||
if exec_used + help_used == 1
|
||||
%w[help exec]
|
||||
else
|
||||
args
|
||||
end
|
||||
elsif help_used
|
||||
args = args.dup
|
||||
args.delete_at(help_used)
|
||||
["help", command || args].flatten.compact
|
||||
else
|
||||
args
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Automatically invoke `bundle install` and resume if
|
||||
# Bundler.settings[:auto_install] exists. This is set through config cmd
|
||||
# `bundle config auto_install 1`.
|
||||
#
|
||||
# Note that this method `nil`s out the global Definition object, so it
|
||||
# should be called first, before you instantiate anything like an
|
||||
# `Installer` that'll keep a reference to the old one instead.
|
||||
def auto_install
|
||||
return unless Bundler.settings[:auto_install]
|
||||
|
||||
begin
|
||||
Bundler.definition.specs
|
||||
rescue GemNotFound
|
||||
Bundler.ui.info "Automatically installing missing gems."
|
||||
Bundler.reset!
|
||||
invoke :install, []
|
||||
Bundler.reset!
|
||||
end
|
||||
end
|
||||
|
||||
def current_command
|
||||
_, _, config = @_initializer
|
||||
config[:current_command]
|
||||
end
|
||||
|
||||
def print_command
|
||||
return unless Bundler.ui.debug?
|
||||
cmd = current_command
|
||||
command_name = cmd.name
|
||||
return if PARSEABLE_COMMANDS.include?(command_name)
|
||||
command = ["bundle", command_name] + args
|
||||
options_to_print = options.dup
|
||||
options_to_print.delete_if do |k, v|
|
||||
next unless o = cmd.options[k]
|
||||
o.default == v
|
||||
end
|
||||
command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip
|
||||
command.reject!(&:empty?)
|
||||
Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}"
|
||||
end
|
||||
|
||||
def warn_on_outdated_bundler
|
||||
return if Bundler.settings[:disable_version_check]
|
||||
|
||||
command_name = current_command.name
|
||||
return if PARSEABLE_COMMANDS.include?(command_name)
|
||||
|
||||
return unless SharedHelpers.md5_available?
|
||||
|
||||
latest = Fetcher::CompactIndex.
|
||||
new(nil, Source::Rubygems::Remote.new(URI("https://rubygems.org")), nil).
|
||||
send(:compact_index_client).
|
||||
instance_variable_get(:@cache).
|
||||
dependencies("bundler").
|
||||
map {|d| Gem::Version.new(d.first) }.
|
||||
max
|
||||
return unless latest
|
||||
|
||||
current = Gem::Version.new(VERSION)
|
||||
return if current >= latest
|
||||
latest_installed = Bundler.rubygems.find_name("bundler").map(&:version).max
|
||||
|
||||
installation = "To install the latest version, run `gem install bundler#{" --pre" if latest.prerelease?}`"
|
||||
if latest_installed && latest_installed > current
|
||||
suggestion = "To update to the most recent installed version (#{latest_installed}), run `bundle update --bundler`"
|
||||
suggestion = "#{installation}\n#{suggestion}" if latest_installed < latest
|
||||
else
|
||||
suggestion = installation
|
||||
end
|
||||
|
||||
Bundler.ui.warn "The latest bundler is #{latest}, but you are currently running #{current}.\n#{suggestion}"
|
||||
rescue RuntimeError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Add
|
||||
def initialize(options, gems)
|
||||
@gems = gems
|
||||
@options = options
|
||||
@options[:group] = @options[:group].split(",").map(&:strip) if !@options[:group].nil? && !@options[:group].empty?
|
||||
end
|
||||
|
||||
def run
|
||||
raise InvalidOption, "You can not specify `--strict` and `--optimistic` at the same time." if @options[:strict] && @options[:optimistic]
|
||||
|
||||
# raise error when no gems are specified
|
||||
raise InvalidOption, "Please specify gems to add." if @gems.empty?
|
||||
|
||||
version = @options[:version].nil? ? nil : @options[:version].split(",").map(&:strip)
|
||||
|
||||
unless version.nil?
|
||||
version.each do |v|
|
||||
raise InvalidOption, "Invalid gem requirement pattern '#{v}'" unless Gem::Requirement::PATTERN =~ v.to_s
|
||||
end
|
||||
end
|
||||
|
||||
dependencies = @gems.map {|g| Bundler::Dependency.new(g, version, @options) }
|
||||
|
||||
Injector.inject(dependencies,
|
||||
:conservative_versioning => @options[:version].nil?, # Perform conservative versioning only when version is not specified
|
||||
:optimistic => @options[:optimistic],
|
||||
:strict => @options[:strict])
|
||||
|
||||
Installer.install(Bundler.root, Bundler.definition) unless @options["skip-install"]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Binstubs
|
||||
attr_reader :options, :gems
|
||||
def initialize(options, gems)
|
||||
@options = options
|
||||
@gems = gems
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.definition.validate_runtime!
|
||||
path_option = options["path"]
|
||||
path_option = nil if path_option && path_option.empty?
|
||||
Bundler.settings.set_command_option :bin, path_option if options["path"]
|
||||
Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
|
||||
installer = Installer.new(Bundler.root, Bundler.definition)
|
||||
|
||||
installer_opts = { :force => options[:force], :binstubs_cmd => true }
|
||||
|
||||
if options[:all]
|
||||
raise InvalidOption, "Cannot specify --all with specific gems" unless gems.empty?
|
||||
@gems = Bundler.definition.specs.map(&:name)
|
||||
installer_opts.delete(:binstubs_cmd)
|
||||
elsif gems.empty?
|
||||
Bundler.ui.error "`bundle binstubs` needs at least one gem to run."
|
||||
exit 1
|
||||
end
|
||||
|
||||
gems.each do |gem_name|
|
||||
spec = Bundler.definition.specs.find {|s| s.name == gem_name }
|
||||
unless spec
|
||||
raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(
|
||||
gem_name, Bundler.definition.specs
|
||||
)
|
||||
end
|
||||
|
||||
if options[:standalone]
|
||||
next Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") if gem_name == "bundler"
|
||||
Bundler.settings.temporary(:path => (Bundler.settings[:path] || Bundler.root)) do
|
||||
installer.generate_standalone_bundler_executable_stubs(spec)
|
||||
end
|
||||
else
|
||||
installer.generate_bundler_executable_stubs(spec, installer_opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Cache
|
||||
attr_reader :options
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.definition.validate_runtime!
|
||||
Bundler.definition.resolve_with_cache!
|
||||
setup_cache_all
|
||||
Bundler.settings.set_command_option_if_given :cache_all_platforms, options["all-platforms"]
|
||||
Bundler.load.cache
|
||||
Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"]
|
||||
Bundler.load.lock
|
||||
rescue GemNotFound => e
|
||||
Bundler.ui.error(e.message)
|
||||
Bundler.ui.warn "Run `bundle install` to install missing gems."
|
||||
exit 1
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_cache_all
|
||||
Bundler.settings.set_command_option_if_given :cache_all, options[:all]
|
||||
|
||||
if Bundler.definition.has_local_dependencies? && !Bundler.feature_flag.cache_all?
|
||||
Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \
|
||||
"to package them as well, please pass the --all flag. This will be the default " \
|
||||
"on Bundler 2.0."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Check
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.settings.set_command_option_if_given :path, options[:path]
|
||||
|
||||
begin
|
||||
definition = Bundler.definition
|
||||
definition.validate_runtime!
|
||||
not_installed = definition.missing_specs
|
||||
rescue GemNotFound, VersionConflict
|
||||
Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies."
|
||||
Bundler.ui.warn "Install missing gems with `bundle install`."
|
||||
exit 1
|
||||
end
|
||||
|
||||
if not_installed.any?
|
||||
Bundler.ui.error "The following gems are missing"
|
||||
not_installed.each {|s| Bundler.ui.error " * #{s.name} (#{s.version})" }
|
||||
Bundler.ui.warn "Install missing gems with `bundle install`"
|
||||
exit 1
|
||||
elsif !Bundler.default_lockfile.file? && Bundler.frozen_bundle?
|
||||
Bundler.ui.error "This bundle has been frozen, but there is no #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} present"
|
||||
exit 1
|
||||
else
|
||||
Bundler.load.lock(:preserve_unknown_sections => true) unless options[:"dry-run"]
|
||||
Bundler.ui.info "The Gemfile's dependencies are satisfied"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Clean
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
require_path_or_force unless options[:"dry-run"]
|
||||
Bundler.load.clean(options[:"dry-run"])
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def require_path_or_force
|
||||
return unless Bundler.use_system_gems? && !options[:force]
|
||||
raise InvalidOption, "Cleaning all the gems on your system is dangerous! " \
|
||||
"If you're sure you want to remove every system gem not in this " \
|
||||
"bundle, run `bundle clean --force`."
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,102 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module CLI::Common
|
||||
def self.output_post_install_messages(messages)
|
||||
return if Bundler.settings["ignore_messages"]
|
||||
messages.to_a.each do |name, msg|
|
||||
print_post_install_message(name, msg) unless Bundler.settings["ignore_messages.#{name}"]
|
||||
end
|
||||
end
|
||||
|
||||
def self.print_post_install_message(name, msg)
|
||||
Bundler.ui.confirm "Post-install message from #{name}:"
|
||||
Bundler.ui.info msg
|
||||
end
|
||||
|
||||
def self.output_without_groups_message
|
||||
return if Bundler.settings[:without].empty?
|
||||
Bundler.ui.confirm without_groups_message
|
||||
end
|
||||
|
||||
def self.without_groups_message
|
||||
groups = Bundler.settings[:without]
|
||||
group_list = [groups[0...-1].join(", "), groups[-1..-1]].
|
||||
reject {|s| s.to_s.empty? }.join(" and ")
|
||||
group_str = (groups.size == 1) ? "group" : "groups"
|
||||
"Gems in the #{group_str} #{group_list} were not installed."
|
||||
end
|
||||
|
||||
def self.select_spec(name, regex_match = nil)
|
||||
specs = []
|
||||
regexp = Regexp.new(name) if regex_match
|
||||
|
||||
Bundler.definition.specs.each do |spec|
|
||||
return spec if spec.name == name
|
||||
specs << spec if regexp && spec.name =~ regexp
|
||||
end
|
||||
|
||||
case specs.count
|
||||
when 0
|
||||
raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
|
||||
when 1
|
||||
specs.first
|
||||
else
|
||||
ask_for_spec_from(specs)
|
||||
end
|
||||
rescue RegexpError
|
||||
raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
|
||||
end
|
||||
|
||||
def self.ask_for_spec_from(specs)
|
||||
if !$stdout.tty? && ENV["BUNDLE_SPEC_RUN"].nil?
|
||||
raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
|
||||
end
|
||||
|
||||
specs.each_with_index do |spec, index|
|
||||
Bundler.ui.info "#{index.succ} : #{spec.name}", true
|
||||
end
|
||||
Bundler.ui.info "0 : - exit -", true
|
||||
|
||||
num = Bundler.ui.ask("> ").to_i
|
||||
num > 0 ? specs[num - 1] : nil
|
||||
end
|
||||
|
||||
def self.gem_not_found_message(missing_gem_name, alternatives)
|
||||
require "bundler/similarity_detector"
|
||||
message = "Could not find gem '#{missing_gem_name}'."
|
||||
alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a }
|
||||
suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name)
|
||||
message += "\nDid you mean #{suggestions}?" if suggestions
|
||||
message
|
||||
end
|
||||
|
||||
def self.ensure_all_gems_in_lockfile!(names, locked_gems = Bundler.locked_gems)
|
||||
locked_names = locked_gems.specs.map(&:name)
|
||||
names.-(locked_names).each do |g|
|
||||
raise GemNotFound, gem_not_found_message(g, locked_names)
|
||||
end
|
||||
end
|
||||
|
||||
def self.configure_gem_version_promoter(definition, options)
|
||||
patch_level = patch_level_options(options)
|
||||
raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1
|
||||
definition.gem_version_promoter.tap do |gvp|
|
||||
gvp.level = patch_level.first || :major
|
||||
gvp.strict = options[:strict] || options["update-strict"]
|
||||
end
|
||||
end
|
||||
|
||||
def self.patch_level_options(options)
|
||||
[:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) }
|
||||
end
|
||||
|
||||
def self.clean_after_install?
|
||||
clean = Bundler.settings[:clean]
|
||||
return clean unless clean.nil?
|
||||
clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil?
|
||||
clean &&= !Bundler.use_system_gems?
|
||||
clean
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,119 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Config
|
||||
attr_reader :name, :options, :scope, :thor
|
||||
attr_accessor :args
|
||||
|
||||
def initialize(options, args, thor)
|
||||
@options = options
|
||||
@args = args
|
||||
@thor = thor
|
||||
@name = peek = args.shift
|
||||
@scope = "global"
|
||||
return unless peek && peek.start_with?("--")
|
||||
@name = args.shift
|
||||
@scope = peek[2..-1]
|
||||
end
|
||||
|
||||
def run
|
||||
unless name
|
||||
confirm_all
|
||||
return
|
||||
end
|
||||
|
||||
unless valid_scope?(scope)
|
||||
Bundler.ui.error "Invalid scope --#{scope} given. Please use --local or --global."
|
||||
exit 1
|
||||
end
|
||||
|
||||
if scope == "delete"
|
||||
Bundler.settings.set_local(name, nil)
|
||||
Bundler.settings.set_global(name, nil)
|
||||
return
|
||||
end
|
||||
|
||||
if args.empty?
|
||||
if options[:parseable]
|
||||
if value = Bundler.settings[name]
|
||||
Bundler.ui.info("#{name}=#{value}")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
confirm(name)
|
||||
return
|
||||
end
|
||||
|
||||
Bundler.ui.info(message) if message
|
||||
Bundler.settings.send("set_#{scope}", name, new_value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def confirm_all
|
||||
if @options[:parseable]
|
||||
thor.with_padding do
|
||||
Bundler.settings.all.each do |setting|
|
||||
val = Bundler.settings[setting]
|
||||
Bundler.ui.info "#{setting}=#{val}"
|
||||
end
|
||||
end
|
||||
else
|
||||
Bundler.ui.confirm "Settings are listed in order of priority. The top value will be used.\n"
|
||||
Bundler.settings.all.each do |setting|
|
||||
Bundler.ui.confirm "#{setting}"
|
||||
show_pretty_values_for(setting)
|
||||
Bundler.ui.confirm ""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def confirm(name)
|
||||
Bundler.ui.confirm "Settings for `#{name}` in order of priority. The top value will be used"
|
||||
show_pretty_values_for(name)
|
||||
end
|
||||
|
||||
def new_value
|
||||
pathname = Pathname.new(args.join(" "))
|
||||
if name.start_with?("local.") && pathname.directory?
|
||||
pathname.expand_path.to_s
|
||||
else
|
||||
args.join(" ")
|
||||
end
|
||||
end
|
||||
|
||||
def message
|
||||
locations = Bundler.settings.locations(name)
|
||||
if @options[:parseable]
|
||||
"#{name}=#{new_value}" if new_value
|
||||
elsif scope == "global"
|
||||
if locations[:local]
|
||||
"Your application has set #{name} to #{locations[:local].inspect}. " \
|
||||
"This will override the global value you are currently setting"
|
||||
elsif locations[:env]
|
||||
"You have a bundler environment variable for #{name} set to " \
|
||||
"#{locations[:env].inspect}. This will take precedence over the global value you are setting"
|
||||
elsif locations[:global] && locations[:global] != args.join(" ")
|
||||
"You are replacing the current global value of #{name}, which is currently " \
|
||||
"#{locations[:global].inspect}"
|
||||
end
|
||||
elsif scope == "local" && locations[:local] != args.join(" ")
|
||||
"You are replacing the current local value of #{name}, which is currently " \
|
||||
"#{locations[:local].inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def show_pretty_values_for(setting)
|
||||
thor.with_padding do
|
||||
Bundler.settings.pretty_values_for(setting).each do |line|
|
||||
Bundler.ui.info line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def valid_scope?(scope)
|
||||
%w[delete local global].include?(scope)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Console
|
||||
attr_reader :options, :group
|
||||
def initialize(options, group)
|
||||
@options = options
|
||||
@group = group
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler::SharedHelpers.major_deprecation 2, "bundle console will be replaced " \
|
||||
"by `bin/console` generated by `bundle gem <name>`"
|
||||
|
||||
group ? Bundler.require(:default, *(group.split.map!(&:to_sym))) : Bundler.require
|
||||
ARGV.clear
|
||||
|
||||
console = get_console(Bundler.settings[:console] || "irb")
|
||||
console.start
|
||||
end
|
||||
|
||||
def get_console(name)
|
||||
require name
|
||||
get_constant(name)
|
||||
rescue LoadError
|
||||
Bundler.ui.error "Couldn't load console #{name}, falling back to irb"
|
||||
require "irb"
|
||||
get_constant("irb")
|
||||
end
|
||||
|
||||
def get_constant(name)
|
||||
const_name = {
|
||||
"pry" => :Pry,
|
||||
"ripl" => :Ripl,
|
||||
"irb" => :IRB,
|
||||
}[name]
|
||||
Object.const_get(const_name)
|
||||
rescue NameError
|
||||
Bundler.ui.error "Could not find constant #{const_name}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,140 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rbconfig"
|
||||
|
||||
module Bundler
|
||||
class CLI::Doctor
|
||||
DARWIN_REGEX = /\s+(.+) \(compatibility /
|
||||
LDD_REGEX = /\t\S+ => (\S+) \(\S+\)/
|
||||
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def otool_available?
|
||||
Bundler.which("otool")
|
||||
end
|
||||
|
||||
def ldd_available?
|
||||
Bundler.which("ldd")
|
||||
end
|
||||
|
||||
def dylibs_darwin(path)
|
||||
output = `/usr/bin/otool -L "#{path}"`.chomp
|
||||
dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq
|
||||
# ignore @rpath and friends
|
||||
dylibs.reject {|dylib| dylib.start_with? "@" }
|
||||
end
|
||||
|
||||
def dylibs_ldd(path)
|
||||
output = `/usr/bin/ldd "#{path}"`.chomp
|
||||
output.split("\n").map do |l|
|
||||
match = l.match(LDD_REGEX)
|
||||
next if match.nil?
|
||||
match.captures[0]
|
||||
end.compact
|
||||
end
|
||||
|
||||
def dylibs(path)
|
||||
case RbConfig::CONFIG["host_os"]
|
||||
when /darwin/
|
||||
return [] unless otool_available?
|
||||
dylibs_darwin(path)
|
||||
when /(linux|solaris|bsd)/
|
||||
return [] unless ldd_available?
|
||||
dylibs_ldd(path)
|
||||
else # Windows, etc.
|
||||
Bundler.ui.warn("Dynamic library check not supported on this platform.")
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def bundles_for_gem(spec)
|
||||
Dir.glob("#{spec.full_gem_path}/**/*.bundle")
|
||||
end
|
||||
|
||||
def check!
|
||||
require "bundler/cli/check"
|
||||
Bundler::CLI::Check.new({}).run
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "error" if options[:quiet]
|
||||
Bundler.settings.validate!
|
||||
check!
|
||||
|
||||
definition = Bundler.definition
|
||||
broken_links = {}
|
||||
|
||||
definition.specs.each do |spec|
|
||||
bundles_for_gem(spec).each do |bundle|
|
||||
bad_paths = dylibs(bundle).select {|f| !File.exist?(f) }
|
||||
if bad_paths.any?
|
||||
broken_links[spec] ||= []
|
||||
broken_links[spec].concat(bad_paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
permissions_valid = check_home_permissions
|
||||
|
||||
if broken_links.any?
|
||||
message = "The following gems are missing OS dependencies:"
|
||||
broken_links.map do |spec, paths|
|
||||
paths.uniq.map do |path|
|
||||
"\n * #{spec.name}: #{path}"
|
||||
end
|
||||
end.flatten.sort.each {|m| message += m }
|
||||
raise ProductionError, message
|
||||
elsif !permissions_valid
|
||||
Bundler.ui.info "No issues found with the installed bundle"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_home_permissions
|
||||
require "find"
|
||||
files_not_readable_or_writable = []
|
||||
files_not_rw_and_owned_by_different_user = []
|
||||
files_not_owned_by_current_user_but_still_rw = []
|
||||
Find.find(Bundler.home.to_s).each do |f|
|
||||
if !File.writable?(f) || !File.readable?(f)
|
||||
if File.stat(f).uid != Process.uid
|
||||
files_not_rw_and_owned_by_different_user << f
|
||||
else
|
||||
files_not_readable_or_writable << f
|
||||
end
|
||||
elsif File.stat(f).uid != Process.uid
|
||||
files_not_owned_by_current_user_but_still_rw << f
|
||||
end
|
||||
end
|
||||
|
||||
ok = true
|
||||
if files_not_owned_by_current_user_but_still_rw.any?
|
||||
Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \
|
||||
"user, but are still readable/writable. These files are:\n - #{files_not_owned_by_current_user_but_still_rw.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
if files_not_rw_and_owned_by_different_user.any?
|
||||
Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \
|
||||
"user, and are not readable/writable. These files are:\n - #{files_not_rw_and_owned_by_different_user.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
if files_not_readable_or_writable.any?
|
||||
Bundler.ui.warn "Files exist in the Bundler home that are not " \
|
||||
"readable/writable by the current user. These files are:\n - #{files_not_readable_or_writable.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
ok
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,105 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/current_ruby"
|
||||
|
||||
module Bundler
|
||||
class CLI::Exec
|
||||
attr_reader :options, :args, :cmd
|
||||
|
||||
TRAPPED_SIGNALS = %w[INT].freeze
|
||||
|
||||
def initialize(options, args)
|
||||
@options = options
|
||||
@cmd = args.shift
|
||||
@args = args
|
||||
|
||||
if Bundler.current_ruby.ruby_2? && !Bundler.current_ruby.jruby?
|
||||
@args << { :close_others => !options.keep_file_descriptors? }
|
||||
elsif options.keep_file_descriptors?
|
||||
Bundler.ui.warn "Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec."
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
validate_cmd!
|
||||
SharedHelpers.set_bundle_environment
|
||||
if bin_path = Bundler.which(cmd)
|
||||
if !Bundler.settings[:disable_exec_load] && ruby_shebang?(bin_path)
|
||||
return kernel_load(bin_path, *args)
|
||||
end
|
||||
# First, try to exec directly to something in PATH
|
||||
if Bundler.current_ruby.jruby_18?
|
||||
kernel_exec(bin_path, *args)
|
||||
else
|
||||
kernel_exec([bin_path, cmd], *args)
|
||||
end
|
||||
else
|
||||
# exec using the given command
|
||||
kernel_exec(cmd, *args)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_cmd!
|
||||
return unless cmd.nil?
|
||||
Bundler.ui.error "bundler: exec needs a command to run"
|
||||
exit 128
|
||||
end
|
||||
|
||||
def kernel_exec(*args)
|
||||
ui = Bundler.ui
|
||||
Bundler.ui = nil
|
||||
Kernel.exec(*args)
|
||||
rescue Errno::EACCES, Errno::ENOEXEC
|
||||
Bundler.ui = ui
|
||||
Bundler.ui.error "bundler: not executable: #{cmd}"
|
||||
exit 126
|
||||
rescue Errno::ENOENT
|
||||
Bundler.ui = ui
|
||||
Bundler.ui.error "bundler: command not found: #{cmd}"
|
||||
Bundler.ui.warn "Install missing gem executables with `bundle install`"
|
||||
exit 127
|
||||
end
|
||||
|
||||
def kernel_load(file, *args)
|
||||
args.pop if args.last.is_a?(Hash)
|
||||
ARGV.replace(args)
|
||||
$0 = file
|
||||
Process.setproctitle(process_title(file, args)) if Process.respond_to?(:setproctitle)
|
||||
ui = Bundler.ui
|
||||
Bundler.ui = nil
|
||||
require "bundler/setup"
|
||||
TRAPPED_SIGNALS.each {|s| trap(s, "DEFAULT") }
|
||||
Kernel.load(file)
|
||||
rescue SystemExit, SignalException
|
||||
raise
|
||||
rescue Exception => e # rubocop:disable Lint/RescueException
|
||||
Bundler.ui = ui
|
||||
Bundler.ui.error "bundler: failed to load command: #{cmd} (#{file})"
|
||||
backtrace = e.backtrace ? e.backtrace.take_while {|bt| !bt.start_with?(__FILE__) } : []
|
||||
abort "#{e.class}: #{e.message}\n #{backtrace.join("\n ")}"
|
||||
end
|
||||
|
||||
def process_title(file, args)
|
||||
"#{file} #{args.join(" ")}".strip
|
||||
end
|
||||
|
||||
def ruby_shebang?(file)
|
||||
possibilities = [
|
||||
"#!/usr/bin/env ruby\n",
|
||||
"#!/usr/bin/env jruby\n",
|
||||
"#!/usr/bin/env truffleruby\n",
|
||||
"#!#{Gem.ruby}\n",
|
||||
]
|
||||
|
||||
if File.zero?(file)
|
||||
Bundler.ui.warn "#{file} is empty"
|
||||
return false
|
||||
end
|
||||
|
||||
first_line = File.open(file, "rb") {|f| f.read(possibilities.map(&:size).max) }
|
||||
possibilities.any? {|shebang| first_line.start_with?(shebang) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,252 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "pathname"
|
||||
|
||||
module Bundler
|
||||
class CLI
|
||||
Bundler.require_thor_actions
|
||||
include Thor::Actions
|
||||
end
|
||||
|
||||
class CLI::Gem
|
||||
TEST_FRAMEWORK_VERSIONS = {
|
||||
"rspec" => "3.0",
|
||||
"minitest" => "5.0"
|
||||
}.freeze
|
||||
|
||||
attr_reader :options, :gem_name, :thor, :name, :target
|
||||
|
||||
def initialize(options, gem_name, thor)
|
||||
@options = options
|
||||
@gem_name = resolve_name(gem_name)
|
||||
|
||||
@thor = thor
|
||||
thor.behavior = :invoke
|
||||
thor.destination_root = nil
|
||||
|
||||
@name = @gem_name
|
||||
@target = SharedHelpers.pwd.join(gem_name)
|
||||
|
||||
validate_ext_name if options[:ext]
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.confirm "Creating gem '#{name}'..."
|
||||
|
||||
underscored_name = name.tr("-", "_")
|
||||
namespaced_path = name.tr("-", "/")
|
||||
constant_name = name.gsub(/-[_-]*(?![_-]|$)/) { "::" }.gsub(/([_-]+|(::)|^)(.|$)/) { $2.to_s + $3.upcase }
|
||||
constant_array = constant_name.split("::")
|
||||
|
||||
git_installed = Bundler.git_present?
|
||||
|
||||
git_author_name = git_installed ? `git config user.name`.chomp : ""
|
||||
github_username = git_installed ? `git config github.user`.chomp : ""
|
||||
git_user_email = git_installed ? `git config user.email`.chomp : ""
|
||||
|
||||
config = {
|
||||
:name => name,
|
||||
:underscored_name => underscored_name,
|
||||
:namespaced_path => namespaced_path,
|
||||
:makefile_path => "#{underscored_name}/#{underscored_name}",
|
||||
:constant_name => constant_name,
|
||||
:constant_array => constant_array,
|
||||
:author => git_author_name.empty? ? "TODO: Write your name" : git_author_name,
|
||||
:email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email,
|
||||
:test => options[:test],
|
||||
:ext => options[:ext],
|
||||
:exe => options[:exe],
|
||||
:bundler_version => bundler_dependency_version,
|
||||
:github_username => github_username.empty? ? "[USERNAME]" : github_username
|
||||
}
|
||||
ensure_safe_gem_name(name, constant_array)
|
||||
|
||||
templates = {
|
||||
"Gemfile.tt" => "Gemfile",
|
||||
"lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb",
|
||||
"lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb",
|
||||
"newgem.gemspec.tt" => "#{name}.gemspec",
|
||||
"Rakefile.tt" => "Rakefile",
|
||||
"README.md.tt" => "README.md",
|
||||
"bin/console.tt" => "bin/console",
|
||||
"bin/setup.tt" => "bin/setup"
|
||||
}
|
||||
|
||||
executables = %w[
|
||||
bin/console
|
||||
bin/setup
|
||||
]
|
||||
|
||||
templates.merge!("gitignore.tt" => ".gitignore") if Bundler.git_present?
|
||||
|
||||
if test_framework = ask_and_set_test_framework
|
||||
config[:test] = test_framework
|
||||
config[:test_framework_version] = TEST_FRAMEWORK_VERSIONS[test_framework]
|
||||
|
||||
templates.merge!("travis.yml.tt" => ".travis.yml")
|
||||
|
||||
case test_framework
|
||||
when "rspec"
|
||||
templates.merge!(
|
||||
"rspec.tt" => ".rspec",
|
||||
"spec/spec_helper.rb.tt" => "spec/spec_helper.rb",
|
||||
"spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb"
|
||||
)
|
||||
when "minitest"
|
||||
templates.merge!(
|
||||
"test/test_helper.rb.tt" => "test/test_helper.rb",
|
||||
"test/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
config[:test_task] = config[:test] == "minitest" ? "test" : "spec"
|
||||
|
||||
if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?",
|
||||
"This means that any other developer or company will be legally allowed to use your code " \
|
||||
"for free as long as they admit you created it. You can read more about the MIT license " \
|
||||
"at https://choosealicense.com/licenses/mit.")
|
||||
config[:mit] = true
|
||||
Bundler.ui.info "MIT License enabled in config"
|
||||
templates.merge!("LICENSE.txt.tt" => "LICENSE.txt")
|
||||
end
|
||||
|
||||
if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?",
|
||||
"Codes of conduct can increase contributions to your project by contributors who " \
|
||||
"prefer collaborative, safe spaces. You can read more about the code of conduct at " \
|
||||
"contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \
|
||||
"of enforcing it, so be sure that you are prepared to do that. Be sure that your email " \
|
||||
"address is specified as a contact in the generated code of conduct so that people know " \
|
||||
"who to contact in case of a violation. For suggestions about " \
|
||||
"how to enforce codes of conduct, see https://bit.ly/coc-enforcement.")
|
||||
config[:coc] = true
|
||||
Bundler.ui.info "Code of conduct enabled in config"
|
||||
templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md")
|
||||
end
|
||||
|
||||
templates.merge!("exe/newgem.tt" => "exe/#{name}") if config[:exe]
|
||||
|
||||
if options[:ext]
|
||||
templates.merge!(
|
||||
"ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb",
|
||||
"ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h",
|
||||
"ext/newgem/newgem.c.tt" => "ext/#{name}/#{underscored_name}.c"
|
||||
)
|
||||
end
|
||||
|
||||
templates.each do |src, dst|
|
||||
destination = target.join(dst)
|
||||
SharedHelpers.filesystem_access(destination) do
|
||||
thor.template("newgem/#{src}", destination, config)
|
||||
end
|
||||
end
|
||||
|
||||
executables.each do |file|
|
||||
SharedHelpers.filesystem_access(target.join(file)) do |path|
|
||||
executable = (path.stat.mode | 0o111)
|
||||
path.chmod(executable)
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.git_present?
|
||||
Bundler.ui.info "Initializing git repo in #{target}"
|
||||
Dir.chdir(target) do
|
||||
`git init`
|
||||
`git add .`
|
||||
end
|
||||
end
|
||||
|
||||
# Open gemspec in editor
|
||||
open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit]
|
||||
|
||||
Bundler.ui.info "Gem '#{name}' was successfully created. " \
|
||||
"For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html"
|
||||
rescue Errno::EEXIST => e
|
||||
raise GenericSystemCallError.new(e, "There was a conflict while creating the new gem.")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolve_name(name)
|
||||
SharedHelpers.pwd.join(name).basename.to_s
|
||||
end
|
||||
|
||||
def ask_and_set(key, header, message)
|
||||
choice = options[key]
|
||||
choice = Bundler.settings["gem.#{key}"] if choice.nil?
|
||||
|
||||
if choice.nil?
|
||||
Bundler.ui.confirm header
|
||||
choice = Bundler.ui.yes? "#{message} y/(n):"
|
||||
Bundler.settings.set_global("gem.#{key}", choice)
|
||||
end
|
||||
|
||||
choice
|
||||
end
|
||||
|
||||
def validate_ext_name
|
||||
return unless gem_name.index("-")
|
||||
|
||||
Bundler.ui.error "You have specified a gem name which does not conform to the \n" \
|
||||
"naming guidelines for C extensions. For more information, \n" \
|
||||
"see the 'Extension Naming' section at the following URL:\n" \
|
||||
"http://guides.rubygems.org/gems-with-extensions/\n"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def ask_and_set_test_framework
|
||||
test_framework = options[:test] || Bundler.settings["gem.test"]
|
||||
|
||||
if test_framework.nil?
|
||||
Bundler.ui.confirm "Do you want to generate tests with your gem?"
|
||||
result = Bundler.ui.ask "Type 'rspec' or 'minitest' to generate those test files now and " \
|
||||
"in the future. rspec/minitest/(none):"
|
||||
if result =~ /rspec|minitest/
|
||||
test_framework = result
|
||||
else
|
||||
test_framework = false
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.settings["gem.test"].nil?
|
||||
Bundler.settings.set_global("gem.test", test_framework)
|
||||
end
|
||||
|
||||
test_framework
|
||||
end
|
||||
|
||||
def bundler_dependency_version
|
||||
v = Gem::Version.new(Bundler::VERSION)
|
||||
req = v.segments[0..1]
|
||||
req << "a" if v.prerelease?
|
||||
req.join(".")
|
||||
end
|
||||
|
||||
def ensure_safe_gem_name(name, constant_array)
|
||||
if name =~ /^\d/
|
||||
Bundler.ui.error "Invalid gem name #{name} Please give a name which does not start with numbers."
|
||||
exit 1
|
||||
end
|
||||
|
||||
constant_name = constant_array.join("::")
|
||||
|
||||
existing_constant = constant_array.inject(Object) do |c, s|
|
||||
defined = begin
|
||||
c.const_defined?(s)
|
||||
rescue NameError
|
||||
Bundler.ui.error "Invalid gem name #{name} -- `#{constant_name}` is an invalid constant name"
|
||||
exit 1
|
||||
end
|
||||
(defined && c.const_get(s)) || break
|
||||
end
|
||||
|
||||
return unless existing_constant
|
||||
Bundler.ui.error "Invalid gem name #{name} constant #{constant_name} is already in use. Please choose another gem name."
|
||||
exit 1
|
||||
end
|
||||
|
||||
def open_editor(editor, file)
|
||||
thor.run(%(#{editor} "#{file}"))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Info
|
||||
attr_reader :gem_name, :options
|
||||
def initialize(options, gem_name)
|
||||
@options = options
|
||||
@gem_name = gem_name
|
||||
end
|
||||
|
||||
def run
|
||||
spec = spec_for_gem(gem_name)
|
||||
|
||||
spec_not_found(gem_name) unless spec
|
||||
return print_gem_path(spec) if @options[:path]
|
||||
print_gem_info(spec)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def spec_for_gem(gem_name)
|
||||
spec = Bundler.definition.specs.find {|s| s.name == gem_name }
|
||||
spec || default_gem_spec(gem_name)
|
||||
end
|
||||
|
||||
def default_gem_spec(gem_name)
|
||||
return unless Gem::Specification.respond_to?(:find_all_by_name)
|
||||
gem_spec = Gem::Specification.find_all_by_name(gem_name).last
|
||||
return gem_spec if gem_spec && gem_spec.respond_to?(:default_gem?) && gem_spec.default_gem?
|
||||
end
|
||||
|
||||
def spec_not_found(gem_name)
|
||||
raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(gem_name, Bundler.definition.dependencies)
|
||||
end
|
||||
|
||||
def print_gem_path(spec)
|
||||
Bundler.ui.info spec.full_gem_path
|
||||
end
|
||||
|
||||
def print_gem_info(spec)
|
||||
gem_info = String.new
|
||||
gem_info << " * #{spec.name} (#{spec.version}#{spec.git_version})\n"
|
||||
gem_info << "\tSummary: #{spec.summary}\n" if spec.summary
|
||||
gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage
|
||||
gem_info << "\tPath: #{spec.full_gem_path}\n"
|
||||
gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem?
|
||||
Bundler.ui.info gem_info
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Init
|
||||
attr_reader :options
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
if File.exist?(gemfile)
|
||||
Bundler.ui.error "#{gemfile} already exists at #{File.expand_path(gemfile)}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
unless File.writable?(Dir.pwd)
|
||||
Bundler.ui.error "Can not create #{gemfile} as the current directory is not writable."
|
||||
exit 1
|
||||
end
|
||||
|
||||
if options[:gemspec]
|
||||
gemspec = File.expand_path(options[:gemspec])
|
||||
unless File.exist?(gemspec)
|
||||
Bundler.ui.error "Gem specification #{gemspec} doesn't exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
spec = Bundler.load_gemspec_uncached(gemspec)
|
||||
|
||||
File.open(gemfile, "wb") do |file|
|
||||
file << "# Generated from #{gemspec}\n"
|
||||
file << spec.to_gemfile
|
||||
end
|
||||
else
|
||||
FileUtils.cp(File.expand_path("../../templates/#{gemfile}", __FILE__), gemfile)
|
||||
end
|
||||
|
||||
puts "Writing new #{gemfile} to #{SharedHelpers.pwd}/#{gemfile}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gemfile
|
||||
@gemfile ||= Bundler.feature_flag.init_gems_rb? ? "gems.rb" : "Gemfile"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Inject
|
||||
attr_reader :options, :name, :version, :group, :source, :gems
|
||||
def initialize(options, name, version)
|
||||
@options = options
|
||||
@name = name
|
||||
@version = version || last_version_number
|
||||
@group = options[:group].split(",") unless options[:group].nil?
|
||||
@source = options[:source]
|
||||
@gems = []
|
||||
end
|
||||
|
||||
def run
|
||||
# The required arguments allow Thor to give useful feedback when the arguments
|
||||
# are incorrect. This adds those first two arguments onto the list as a whole.
|
||||
gems.unshift(source).unshift(group).unshift(version).unshift(name)
|
||||
|
||||
# Build an array of Dependency objects out of the arguments
|
||||
deps = []
|
||||
# when `inject` support addition of more than one gem, then this loop will
|
||||
# help. Currently this loop is running once.
|
||||
gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source|
|
||||
ops = Gem::Requirement::OPS.map {|key, _val| key }
|
||||
has_op = ops.any? {|op| gem_version.start_with? op }
|
||||
gem_version = "~> #{gem_version}" unless has_op
|
||||
deps << Bundler::Dependency.new(gem_name, gem_version, "group" => gem_group, "source" => gem_source)
|
||||
end
|
||||
|
||||
added = Injector.inject(deps, options)
|
||||
|
||||
if added.any?
|
||||
Bundler.ui.confirm "Added to Gemfile:"
|
||||
Bundler.ui.confirm(added.map do |d|
|
||||
name = "'#{d.name}'"
|
||||
requirement = ", '#{d.requirement}'"
|
||||
group = ", :group => #{d.groups.inspect}" if d.groups != Array(:default)
|
||||
source = ", :source => '#{d.source}'" unless d.source.nil?
|
||||
%(gem #{name}#{requirement}#{group}#{source})
|
||||
end.join("\n"))
|
||||
else
|
||||
Bundler.ui.confirm "All gems were already present in the Gemfile"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def last_version_number
|
||||
definition = Bundler.definition(true)
|
||||
definition.resolve_remotely!
|
||||
specs = definition.index[name].sort_by(&:version)
|
||||
unless options[:pre]
|
||||
specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
|
||||
end
|
||||
spec = specs.last
|
||||
spec.version.to_s
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,217 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Install
|
||||
attr_reader :options
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "error" if options[:quiet]
|
||||
|
||||
warn_if_root
|
||||
|
||||
normalize_groups
|
||||
|
||||
Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Bundler::FREEBSD
|
||||
|
||||
# Disable color in deployment mode
|
||||
Bundler.ui.shell = Thor::Shell::Basic.new if options[:deployment]
|
||||
|
||||
check_for_options_conflicts
|
||||
|
||||
check_trust_policy
|
||||
|
||||
if options[:deployment] || options[:frozen] || Bundler.frozen_bundle?
|
||||
unless Bundler.default_lockfile.exist?
|
||||
flag = "--deployment flag" if options[:deployment]
|
||||
flag ||= "--frozen flag" if options[:frozen]
|
||||
flag ||= "deployment setting"
|
||||
raise ProductionError, "The #{flag} requires a #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}. Please make " \
|
||||
"sure you have checked your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} into version control " \
|
||||
"before deploying."
|
||||
end
|
||||
|
||||
options[:local] = true if Bundler.app_cache.exist?
|
||||
|
||||
if Bundler.feature_flag.deployment_means_frozen?
|
||||
Bundler.settings.set_command_option :deployment, true
|
||||
else
|
||||
Bundler.settings.set_command_option :frozen, true
|
||||
end
|
||||
end
|
||||
|
||||
# When install is called with --no-deployment, disable deployment mode
|
||||
if options[:deployment] == false
|
||||
Bundler.settings.set_command_option :frozen, nil
|
||||
options[:system] = true
|
||||
end
|
||||
|
||||
normalize_settings
|
||||
|
||||
Bundler::Fetcher.disable_endpoint = options["full-index"]
|
||||
|
||||
if options["binstubs"]
|
||||
Bundler::SharedHelpers.major_deprecation 2,
|
||||
"The --binstubs option will be removed in favor of `bundle binstubs`"
|
||||
end
|
||||
|
||||
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
|
||||
|
||||
definition = Bundler.definition
|
||||
definition.validate_runtime!
|
||||
|
||||
installer = Installer.install(Bundler.root, definition, options)
|
||||
Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.frozen_bundle?
|
||||
|
||||
Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}."
|
||||
Bundler::CLI::Common.output_without_groups_message
|
||||
|
||||
if Bundler.use_system_gems?
|
||||
Bundler.ui.confirm "Use `bundle info [gemname]` to see where a bundled gem is installed."
|
||||
else
|
||||
relative_path = Bundler.configured_bundle_path.base_path_relative_to_pwd
|
||||
Bundler.ui.confirm "Bundled gems are installed into `#{relative_path}`"
|
||||
end
|
||||
|
||||
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
|
||||
|
||||
warn_ambiguous_gems
|
||||
|
||||
if CLI::Common.clean_after_install?
|
||||
require "bundler/cli/clean"
|
||||
Bundler::CLI::Clean.new(options).run
|
||||
end
|
||||
rescue GemNotFound, VersionConflict => e
|
||||
if options[:local] && Bundler.app_cache.exist?
|
||||
Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory."
|
||||
end
|
||||
|
||||
unless Bundler.definition.has_rubygems_remotes?
|
||||
Bundler.ui.warn <<-WARN, :wrap => true
|
||||
Your Gemfile has no gem server sources. If you need gems that are \
|
||||
not already on your machine, add a line like this to your Gemfile:
|
||||
source 'https://rubygems.org'
|
||||
WARN
|
||||
end
|
||||
raise e
|
||||
rescue Gem::InvalidSpecificationException => e
|
||||
Bundler.ui.warn "You have one or more invalid gemspecs that need to be fixed."
|
||||
raise e
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def warn_if_root
|
||||
return if Bundler.settings[:silence_root_warning] || Bundler::WINDOWS || !Process.uid.zero?
|
||||
Bundler.ui.warn "Don't run Bundler as root. Bundler can ask for sudo " \
|
||||
"if it is needed, and installing your bundle as root will break this " \
|
||||
"application for all non-root users on this machine.", :wrap => true
|
||||
end
|
||||
|
||||
def dependencies_count_for(definition)
|
||||
count = definition.dependencies.count
|
||||
"#{count} Gemfile #{count == 1 ? "dependency" : "dependencies"}"
|
||||
end
|
||||
|
||||
def gems_installed_for(definition)
|
||||
count = definition.specs.count
|
||||
"#{count} #{count == 1 ? "gem" : "gems"} now installed"
|
||||
end
|
||||
|
||||
def check_for_group_conflicts_in_cli_options
|
||||
conflicting_groups = Array(options[:without]) & Array(options[:with])
|
||||
return if conflicting_groups.empty?
|
||||
raise InvalidOption, "You can't list a group in both with and without." \
|
||||
" The offending groups are: #{conflicting_groups.join(", ")}."
|
||||
end
|
||||
|
||||
def check_for_options_conflicts
|
||||
if (options[:path] || options[:deployment]) && options[:system]
|
||||
error_message = String.new
|
||||
error_message << "You have specified both --path as well as --system. Please choose only one option.\n" if options[:path]
|
||||
error_message << "You have specified both --deployment as well as --system. Please choose only one option.\n" if options[:deployment]
|
||||
raise InvalidOption.new(error_message)
|
||||
end
|
||||
end
|
||||
|
||||
def check_trust_policy
|
||||
trust_policy = options["trust-policy"]
|
||||
unless Bundler.rubygems.security_policies.keys.unshift(nil).include?(trust_policy)
|
||||
raise InvalidOption, "RubyGems doesn't know about trust policy '#{trust_policy}'. " \
|
||||
"The known policies are: #{Bundler.rubygems.security_policies.keys.join(", ")}."
|
||||
end
|
||||
Bundler.settings.set_command_option_if_given :"trust-policy", trust_policy
|
||||
end
|
||||
|
||||
def normalize_groups
|
||||
options[:with] &&= options[:with].join(":").tr(" ", ":").split(":")
|
||||
options[:without] &&= options[:without].join(":").tr(" ", ":").split(":")
|
||||
|
||||
check_for_group_conflicts_in_cli_options
|
||||
|
||||
Bundler.settings.set_command_option :with, nil if options[:with] == []
|
||||
Bundler.settings.set_command_option :without, nil if options[:without] == []
|
||||
|
||||
with = options.fetch(:with, [])
|
||||
with |= Bundler.settings[:with].map(&:to_s)
|
||||
with -= options[:without] if options[:without]
|
||||
|
||||
without = options.fetch(:without, [])
|
||||
without |= Bundler.settings[:without].map(&:to_s)
|
||||
without -= options[:with] if options[:with]
|
||||
|
||||
options[:with] = with
|
||||
options[:without] = without
|
||||
end
|
||||
|
||||
def normalize_settings
|
||||
Bundler.settings.set_command_option :path, nil if options[:system]
|
||||
Bundler.settings.temporary(:path_relative_to_cwd => false) do
|
||||
Bundler.settings.set_command_option :path, "vendor/bundle" if options[:deployment]
|
||||
end
|
||||
Bundler.settings.set_command_option_if_given :path, options[:path]
|
||||
Bundler.settings.temporary(:path_relative_to_cwd => false) do
|
||||
Bundler.settings.set_command_option :path, "bundle" if options["standalone"] && Bundler.settings[:path].nil?
|
||||
end
|
||||
|
||||
bin_option = options["binstubs"]
|
||||
bin_option = nil if bin_option && bin_option.empty?
|
||||
Bundler.settings.set_command_option :bin, bin_option if options["binstubs"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :jobs, options["jobs"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :no_install, options["no-install"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :clean, options["clean"]
|
||||
|
||||
unless Bundler.settings[:without] == options[:without] && Bundler.settings[:with] == options[:with]
|
||||
# need to nil them out first to get around validation for backwards compatibility
|
||||
Bundler.settings.set_command_option :without, nil
|
||||
Bundler.settings.set_command_option :with, nil
|
||||
Bundler.settings.set_command_option :without, options[:without] - options[:with]
|
||||
Bundler.settings.set_command_option :with, options[:with]
|
||||
end
|
||||
|
||||
options[:force] = options[:redownload]
|
||||
end
|
||||
|
||||
def warn_ambiguous_gems
|
||||
Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris|
|
||||
Bundler.ui.error "Warning: the gem '#{name}' was found in multiple sources."
|
||||
Bundler.ui.error "Installed from: #{installed_from_uri}"
|
||||
Bundler.ui.error "Also found in:"
|
||||
also_found_in_uris.each {|uri| Bundler.ui.error " * #{uri}" }
|
||||
Bundler.ui.error "You should add a source requirement to restrict this gem to your preferred source."
|
||||
Bundler.ui.error "For example:"
|
||||
Bundler.ui.error " gem '#{name}', :source => '#{installed_from_uri}'"
|
||||
Bundler.ui.error "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rbconfig"
|
||||
|
||||
module Bundler
|
||||
class CLI::Issue
|
||||
def run
|
||||
Bundler.ui.info <<-EOS.gsub(/^ {8}/, "")
|
||||
Did you find an issue with Bundler? Before filing a new issue,
|
||||
be sure to check out these resources:
|
||||
|
||||
1. Check out our troubleshooting guide for quick fixes to common issues:
|
||||
https://github.com/bundler/bundler/blob/master/doc/TROUBLESHOOTING.md
|
||||
|
||||
2. Instructions for common Bundler uses can be found on the documentation
|
||||
site: http://bundler.io/
|
||||
|
||||
3. Information about each Bundler command can be found in the Bundler
|
||||
man pages: http://bundler.io/man/bundle.1.html
|
||||
|
||||
Hopefully the troubleshooting steps above resolved your problem! If things
|
||||
still aren't working the way you expect them to, please let us know so
|
||||
that we can diagnose and help fix the problem you're having. Please
|
||||
view the Filing Issues guide for more information:
|
||||
https://github.com/bundler/bundler/blob/master/doc/contributing/ISSUES.md
|
||||
|
||||
EOS
|
||||
|
||||
Bundler.ui.info Bundler::Env.report
|
||||
|
||||
Bundler.ui.info "\n## Bundle Doctor"
|
||||
doctor
|
||||
end
|
||||
|
||||
def doctor
|
||||
require "bundler/cli/doctor"
|
||||
Bundler::CLI::Doctor.new({}).run
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::List
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
raise InvalidOption, "The `--only-group` and `--without-group` options cannot be used together" if @options["only-group"] && @options["without-group"]
|
||||
|
||||
raise InvalidOption, "The `--name-only` and `--paths` options cannot be used together" if @options["name-only"] && @options[:paths]
|
||||
|
||||
specs = if @options["only-group"] || @options["without-group"]
|
||||
filtered_specs_by_groups
|
||||
else
|
||||
Bundler.load.specs
|
||||
end.reject {|s| s.name == "bundler" }.sort_by(&:name)
|
||||
|
||||
return Bundler.ui.info "No gems in the Gemfile" if specs.empty?
|
||||
|
||||
return specs.each {|s| Bundler.ui.info s.name } if @options["name-only"]
|
||||
return specs.each {|s| Bundler.ui.info s.full_gem_path } if @options["paths"]
|
||||
|
||||
Bundler.ui.info "Gems included by the bundle:"
|
||||
|
||||
specs.each {|s| Bundler.ui.info " * #{s.name} (#{s.version}#{s.git_version})" }
|
||||
|
||||
Bundler.ui.info "Use `bundle info` to print more detailed information about a gem"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_group_exists(groups)
|
||||
raise InvalidOption, "`#{@options["without-group"]}` group could not be found." if @options["without-group"] && !groups.include?(@options["without-group"].to_sym)
|
||||
|
||||
raise InvalidOption, "`#{@options["only-group"]}` group could not be found." if @options["only-group"] && !groups.include?(@options["only-group"].to_sym)
|
||||
end
|
||||
|
||||
def filtered_specs_by_groups
|
||||
definition = Bundler.definition
|
||||
groups = definition.groups
|
||||
|
||||
verify_group_exists(groups)
|
||||
|
||||
show_groups =
|
||||
if @options["without-group"]
|
||||
groups.reject {|g| g == @options["without-group"].to_sym }
|
||||
elsif @options["only-group"]
|
||||
groups.select {|g| g == @options["only-group"].to_sym }
|
||||
else
|
||||
groups
|
||||
end.map(&:to_sym)
|
||||
|
||||
definition.specs_for(show_groups)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Lock
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
unless Bundler.default_gemfile
|
||||
Bundler.ui.error "Unable to find a Gemfile to lock"
|
||||
exit 1
|
||||
end
|
||||
|
||||
print = options[:print]
|
||||
ui = Bundler.ui
|
||||
Bundler.ui = UI::Silent.new if print
|
||||
|
||||
Bundler::Fetcher.disable_endpoint = options["full-index"]
|
||||
|
||||
update = options[:update]
|
||||
if update.is_a?(Array) # unlocking specific gems
|
||||
Bundler::CLI::Common.ensure_all_gems_in_lockfile!(update)
|
||||
update = { :gems => update, :lock_shared_dependencies => options[:conservative] }
|
||||
end
|
||||
definition = Bundler.definition(update)
|
||||
|
||||
Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options) if options[:update]
|
||||
|
||||
options["remove-platform"].each do |platform|
|
||||
definition.remove_platform(platform)
|
||||
end
|
||||
|
||||
options["add-platform"].each do |platform_string|
|
||||
platform = Gem::Platform.new(platform_string)
|
||||
if platform.to_s == "unknown"
|
||||
Bundler.ui.warn "The platform `#{platform_string}` is unknown to RubyGems " \
|
||||
"and adding it will likely lead to resolution errors"
|
||||
end
|
||||
definition.add_platform(platform)
|
||||
end
|
||||
|
||||
if definition.platforms.empty?
|
||||
raise InvalidOption, "Removing all platforms from the bundle is not allowed"
|
||||
end
|
||||
|
||||
definition.resolve_remotely! unless options[:local]
|
||||
|
||||
if print
|
||||
puts definition.to_lock
|
||||
else
|
||||
file = options[:lockfile]
|
||||
file = file ? File.expand_path(file) : Bundler.default_lockfile
|
||||
puts "Writing lockfile to #{file}"
|
||||
definition.lock(file)
|
||||
end
|
||||
|
||||
Bundler.ui = ui
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "shellwords"
|
||||
|
||||
module Bundler
|
||||
class CLI::Open
|
||||
attr_reader :options, :name
|
||||
def initialize(options, name)
|
||||
@options = options
|
||||
@name = name
|
||||
end
|
||||
|
||||
def run
|
||||
editor = [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }
|
||||
return Bundler.ui.info("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") unless editor
|
||||
return unless spec = Bundler::CLI::Common.select_spec(name, :regex_match)
|
||||
path = spec.full_gem_path
|
||||
Dir.chdir(path) do
|
||||
command = Shellwords.split(editor) + [path]
|
||||
Bundler.with_original_env do
|
||||
system(*command)
|
||||
end || Bundler.ui.info("Could not run '#{command.join(" ")}'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,266 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Outdated
|
||||
attr_reader :options, :gems
|
||||
|
||||
def initialize(options, gems)
|
||||
@options = options
|
||||
@gems = gems
|
||||
end
|
||||
|
||||
def run
|
||||
check_for_deployment_mode
|
||||
|
||||
sources = Array(options[:source])
|
||||
|
||||
gems.each do |gem_name|
|
||||
Bundler::CLI::Common.select_spec(gem_name)
|
||||
end
|
||||
|
||||
Bundler.definition.validate_runtime!
|
||||
current_specs = Bundler.ui.silence { Bundler.definition.resolve }
|
||||
current_dependencies = {}
|
||||
Bundler.ui.silence do
|
||||
Bundler.load.dependencies.each do |dep|
|
||||
current_dependencies[dep.name] = dep
|
||||
end
|
||||
end
|
||||
|
||||
definition = if gems.empty? && sources.empty?
|
||||
# We're doing a full update
|
||||
Bundler.definition(true)
|
||||
else
|
||||
Bundler.definition(:gems => gems, :sources => sources)
|
||||
end
|
||||
|
||||
Bundler::CLI::Common.configure_gem_version_promoter(
|
||||
Bundler.definition,
|
||||
options
|
||||
)
|
||||
|
||||
# the patch level options imply strict is also true. It wouldn't make
|
||||
# sense otherwise.
|
||||
strict = options[:strict] ||
|
||||
Bundler::CLI::Common.patch_level_options(options).any?
|
||||
|
||||
filter_options_patch = options.keys &
|
||||
%w[filter-major filter-minor filter-patch]
|
||||
|
||||
definition_resolution = proc do
|
||||
options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely!
|
||||
end
|
||||
|
||||
if options[:parseable]
|
||||
Bundler.ui.silence(&definition_resolution)
|
||||
else
|
||||
definition_resolution.call
|
||||
end
|
||||
|
||||
Bundler.ui.info ""
|
||||
outdated_gems_by_groups = {}
|
||||
outdated_gems_list = []
|
||||
|
||||
# Loop through the current specs
|
||||
gemfile_specs, dependency_specs = current_specs.partition do |spec|
|
||||
current_dependencies.key? spec.name
|
||||
end
|
||||
|
||||
specs = if options["only-explicit"]
|
||||
gemfile_specs
|
||||
else
|
||||
gemfile_specs + dependency_specs
|
||||
end
|
||||
|
||||
specs.sort_by(&:name).each do |current_spec|
|
||||
next if !gems.empty? && !gems.include?(current_spec.name)
|
||||
|
||||
dependency = current_dependencies[current_spec.name]
|
||||
active_spec = retrieve_active_spec(strict, definition, current_spec)
|
||||
|
||||
next if active_spec.nil?
|
||||
if filter_options_patch.any?
|
||||
update_present = update_present_via_semver_portions(current_spec, active_spec, options)
|
||||
next unless update_present
|
||||
end
|
||||
|
||||
gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version)
|
||||
next unless gem_outdated || (current_spec.git_version != active_spec.git_version)
|
||||
groups = nil
|
||||
if dependency && !options[:parseable]
|
||||
groups = dependency.groups.join(", ")
|
||||
end
|
||||
|
||||
outdated_gems_list << { :active_spec => active_spec,
|
||||
:current_spec => current_spec,
|
||||
:dependency => dependency,
|
||||
:groups => groups }
|
||||
|
||||
outdated_gems_by_groups[groups] ||= []
|
||||
outdated_gems_by_groups[groups] << { :active_spec => active_spec,
|
||||
:current_spec => current_spec,
|
||||
:dependency => dependency,
|
||||
:groups => groups }
|
||||
end
|
||||
|
||||
if outdated_gems_list.empty?
|
||||
display_nothing_outdated_message(filter_options_patch)
|
||||
else
|
||||
unless options[:parseable]
|
||||
if options[:pre]
|
||||
Bundler.ui.info "Outdated gems included in the bundle (including " \
|
||||
"pre-releases):"
|
||||
else
|
||||
Bundler.ui.info "Outdated gems included in the bundle:"
|
||||
end
|
||||
end
|
||||
|
||||
options_include_groups = [:group, :groups].select do |v|
|
||||
options.keys.include?(v.to_s)
|
||||
end
|
||||
|
||||
if options_include_groups.any?
|
||||
ordered_groups = outdated_gems_by_groups.keys.compact.sort
|
||||
[nil, ordered_groups].flatten.each do |groups|
|
||||
gems = outdated_gems_by_groups[groups]
|
||||
contains_group = if groups
|
||||
groups.split(",").include?(options[:group])
|
||||
else
|
||||
options[:group] == "group"
|
||||
end
|
||||
|
||||
next if (!options[:groups] && !contains_group) || gems.nil?
|
||||
|
||||
unless options[:parseable]
|
||||
if groups
|
||||
Bundler.ui.info "===== Group #{groups} ====="
|
||||
else
|
||||
Bundler.ui.info "===== Without group ====="
|
||||
end
|
||||
end
|
||||
|
||||
gems.each do |gem|
|
||||
print_gem(
|
||||
gem[:current_spec],
|
||||
gem[:active_spec],
|
||||
gem[:dependency],
|
||||
groups,
|
||||
options_include_groups.any?
|
||||
)
|
||||
end
|
||||
end
|
||||
else
|
||||
outdated_gems_list.each do |gem|
|
||||
print_gem(
|
||||
gem[:current_spec],
|
||||
gem[:active_spec],
|
||||
gem[:dependency],
|
||||
gem[:groups],
|
||||
options_include_groups.any?
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def retrieve_active_spec(strict, definition, current_spec)
|
||||
if strict
|
||||
active_spec = definition.find_resolved_spec(current_spec)
|
||||
else
|
||||
active_specs = definition.find_indexed_specs(current_spec)
|
||||
if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
|
||||
active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
|
||||
end
|
||||
active_spec = active_specs.last
|
||||
end
|
||||
|
||||
active_spec
|
||||
end
|
||||
|
||||
def display_nothing_outdated_message(filter_options_patch)
|
||||
unless options[:parseable]
|
||||
if filter_options_patch.any?
|
||||
display = filter_options_patch.map do |o|
|
||||
o.sub("filter-", "")
|
||||
end.join(" or ")
|
||||
|
||||
Bundler.ui.info "No #{display} updates to display.\n"
|
||||
else
|
||||
Bundler.ui.info "Bundle up to date!\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def print_gem(current_spec, active_spec, dependency, groups, options_include_groups)
|
||||
spec_version = "#{active_spec.version}#{active_spec.git_version}"
|
||||
spec_version += " (from #{active_spec.loaded_from})" if Bundler.ui.debug? && active_spec.loaded_from
|
||||
current_version = "#{current_spec.version}#{current_spec.git_version}"
|
||||
|
||||
if dependency && dependency.specific?
|
||||
dependency_version = %(, requested #{dependency.requirement})
|
||||
end
|
||||
|
||||
spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, " \
|
||||
"installed #{current_version}#{dependency_version})"
|
||||
|
||||
output_message = if options[:parseable]
|
||||
spec_outdated_info.to_s
|
||||
elsif options_include_groups || !groups
|
||||
" * #{spec_outdated_info}"
|
||||
else
|
||||
" * #{spec_outdated_info} in groups \"#{groups}\""
|
||||
end
|
||||
|
||||
Bundler.ui.info output_message.rstrip
|
||||
end
|
||||
|
||||
def check_for_deployment_mode
|
||||
return unless Bundler.frozen_bundle?
|
||||
suggested_command = if Bundler.settings.locations("frozen")[:global]
|
||||
"bundle config --delete frozen"
|
||||
elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
|
||||
"bundle config --delete deployment"
|
||||
else
|
||||
"bundle install --no-deployment"
|
||||
end
|
||||
raise ProductionError, "You are trying to check outdated gems in " \
|
||||
"deployment mode. Run `bundle outdated` elsewhere.\n" \
|
||||
"\nIf this is a development machine, remove the " \
|
||||
"#{Bundler.default_gemfile} freeze" \
|
||||
"\nby running `#{suggested_command}`."
|
||||
end
|
||||
|
||||
def update_present_via_semver_portions(current_spec, active_spec, options)
|
||||
current_major = current_spec.version.segments.first
|
||||
active_major = active_spec.version.segments.first
|
||||
|
||||
update_present = false
|
||||
update_present = active_major > current_major if options["filter-major"]
|
||||
|
||||
if !update_present && (options["filter-minor"] || options["filter-patch"]) && current_major == active_major
|
||||
current_minor = get_version_semver_portion_value(current_spec, 1)
|
||||
active_minor = get_version_semver_portion_value(active_spec, 1)
|
||||
|
||||
update_present = active_minor > current_minor if options["filter-minor"]
|
||||
|
||||
if !update_present && options["filter-patch"] && current_minor == active_minor
|
||||
current_patch = get_version_semver_portion_value(current_spec, 2)
|
||||
active_patch = get_version_semver_portion_value(active_spec, 2)
|
||||
|
||||
update_present = active_patch > current_patch
|
||||
end
|
||||
end
|
||||
|
||||
update_present
|
||||
end
|
||||
|
||||
def get_version_semver_portion_value(spec, version_portion_index)
|
||||
version_section = spec.version.segments[version_portion_index, 1]
|
||||
version_section.nil? ? 0 : (version_section.first || 0)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Package
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "error" if options[:quiet]
|
||||
Bundler.settings.set_command_option_if_given :path, options[:path]
|
||||
Bundler.settings.set_command_option_if_given :cache_all_platforms, options["all-platforms"]
|
||||
Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"]
|
||||
|
||||
setup_cache_all
|
||||
install
|
||||
|
||||
# TODO: move cache contents here now that all bundles are locked
|
||||
custom_path = Bundler.settings[:path] if options[:path]
|
||||
Bundler.load.cache(custom_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def install
|
||||
require "bundler/cli/install"
|
||||
options = self.options.dup
|
||||
if Bundler.settings[:cache_all_platforms]
|
||||
options["local"] = false
|
||||
options["update"] = true
|
||||
end
|
||||
Bundler::CLI::Install.new(options).run
|
||||
end
|
||||
|
||||
def setup_cache_all
|
||||
all = options.fetch(:all, Bundler.feature_flag.cache_command_is_package? || nil)
|
||||
|
||||
Bundler.settings.set_command_option_if_given :cache_all, all
|
||||
|
||||
if Bundler.definition.has_local_dependencies? && !Bundler.feature_flag.cache_all?
|
||||
Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \
|
||||
"to package them as well, please pass the --all flag. This will be the default " \
|
||||
"on Bundler 2.0."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Platform
|
||||
attr_reader :options
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
platforms, ruby_version = Bundler.ui.silence do
|
||||
locked_ruby_version = Bundler.locked_gems && Bundler.locked_gems.ruby_version
|
||||
gemfile_ruby_version = Bundler.definition.ruby_version && Bundler.definition.ruby_version.single_version_string
|
||||
[Bundler.definition.platforms.map {|p| "* #{p}" },
|
||||
locked_ruby_version || gemfile_ruby_version]
|
||||
end
|
||||
output = []
|
||||
|
||||
if options[:ruby]
|
||||
if ruby_version
|
||||
output << ruby_version
|
||||
else
|
||||
output << "No ruby version specified"
|
||||
end
|
||||
else
|
||||
output << "Your platform is: #{RUBY_PLATFORM}"
|
||||
output << "Your app has gems that work on these platforms:\n#{platforms.join("\n")}"
|
||||
|
||||
if ruby_version
|
||||
output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}"
|
||||
|
||||
begin
|
||||
Bundler.definition.validate_runtime!
|
||||
output << "Your current platform satisfies the Ruby version requirement."
|
||||
rescue RubyVersionMismatch => e
|
||||
output << e.message
|
||||
end
|
||||
else
|
||||
output << "Your Gemfile does not specify a Ruby version requirement."
|
||||
end
|
||||
end
|
||||
|
||||
Bundler.ui.info output.join("\n\n")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/vendored_thor"
|
||||
module Bundler
|
||||
class CLI::Plugin < Thor
|
||||
desc "install PLUGINS", "Install the plugin from the source"
|
||||
long_desc <<-D
|
||||
Install plugins either from the rubygems source provided (with --source option) or from a git source provided with (--git option). If no sources are provided, it uses Gem.sources
|
||||
D
|
||||
method_option "source", :type => :string, :default => nil, :banner =>
|
||||
"URL of the RubyGems source to fetch the plugin from"
|
||||
method_option "version", :type => :string, :default => nil, :banner =>
|
||||
"The version of the plugin to fetch"
|
||||
method_option "git", :type => :string, :default => nil, :banner =>
|
||||
"URL of the git repo to fetch from"
|
||||
method_option "branch", :type => :string, :default => nil, :banner =>
|
||||
"The git branch to checkout"
|
||||
method_option "ref", :type => :string, :default => nil, :banner =>
|
||||
"The git revision to check out"
|
||||
def install(*plugins)
|
||||
Bundler::Plugin.install(plugins, options)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Pristine
|
||||
def initialize(gems)
|
||||
@gems = gems
|
||||
end
|
||||
|
||||
def run
|
||||
CLI::Common.ensure_all_gems_in_lockfile!(@gems)
|
||||
definition = Bundler.definition
|
||||
definition.validate_runtime!
|
||||
installer = Bundler::Installer.new(Bundler.root, definition)
|
||||
|
||||
Bundler.load.specs.each do |spec|
|
||||
next if spec.name == "bundler" # Source::Rubygems doesn't install bundler
|
||||
next if !@gems.empty? && !@gems.include?(spec.name)
|
||||
|
||||
gem_name = "#{spec.name} (#{spec.version}#{spec.git_version})"
|
||||
gem_name += " (#{spec.platform})" if !spec.platform.nil? && spec.platform != Gem::Platform::RUBY
|
||||
|
||||
case source = spec.source
|
||||
when Source::Rubygems
|
||||
cached_gem = spec.cache_file
|
||||
unless File.exist?(cached_gem)
|
||||
Bundler.ui.error("Failed to pristine #{gem_name}. Cached gem #{cached_gem} does not exist.")
|
||||
next
|
||||
end
|
||||
|
||||
FileUtils.rm_rf spec.full_gem_path
|
||||
when Source::Git
|
||||
source.remote!
|
||||
if extension_cache_path = source.extension_cache_path(spec)
|
||||
FileUtils.rm_rf extension_cache_path
|
||||
end
|
||||
FileUtils.rm_rf spec.extension_dir if spec.respond_to?(:extension_dir)
|
||||
FileUtils.rm_rf spec.full_gem_path
|
||||
else
|
||||
Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.")
|
||||
next
|
||||
end
|
||||
|
||||
Bundler::GemInstaller.new(spec, installer, false, 0, true).install_from_spec
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Remove
|
||||
def initialize(gems, options)
|
||||
@gems = gems
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
raise InvalidOption, "Please specify gems to remove." if @gems.empty?
|
||||
|
||||
Injector.remove(@gems, {})
|
||||
|
||||
Installer.install(Bundler.root, Bundler.definition) if @options["install"]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,75 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Show
|
||||
attr_reader :options, :gem_name, :latest_specs
|
||||
def initialize(options, gem_name)
|
||||
@options = options
|
||||
@gem_name = gem_name
|
||||
@verbose = options[:verbose] || options[:outdated]
|
||||
@latest_specs = fetch_latest_specs if @verbose
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.silence do
|
||||
Bundler.definition.validate_runtime!
|
||||
Bundler.load.lock
|
||||
end
|
||||
|
||||
if gem_name
|
||||
if gem_name == "bundler"
|
||||
path = File.expand_path("../../../..", __FILE__)
|
||||
else
|
||||
spec = Bundler::CLI::Common.select_spec(gem_name, :regex_match)
|
||||
return unless spec
|
||||
path = spec.full_gem_path
|
||||
unless File.directory?(path)
|
||||
Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at:"
|
||||
end
|
||||
end
|
||||
return Bundler.ui.info(path)
|
||||
end
|
||||
|
||||
if options[:paths]
|
||||
Bundler.load.specs.sort_by(&:name).map do |s|
|
||||
Bundler.ui.info s.full_gem_path
|
||||
end
|
||||
else
|
||||
Bundler.ui.info "Gems included by the bundle:"
|
||||
Bundler.load.specs.sort_by(&:name).each do |s|
|
||||
desc = " * #{s.name} (#{s.version}#{s.git_version})"
|
||||
if @verbose
|
||||
latest = latest_specs.find {|l| l.name == s.name }
|
||||
Bundler.ui.info <<-END.gsub(/^ +/, "")
|
||||
#{desc}
|
||||
\tSummary: #{s.summary || "No description available."}
|
||||
\tHomepage: #{s.homepage || "No website available."}
|
||||
\tStatus: #{outdated?(s, latest) ? "Outdated - #{s.version} < #{latest.version}" : "Up to date"}
|
||||
END
|
||||
else
|
||||
Bundler.ui.info desc
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_latest_specs
|
||||
definition = Bundler.definition(true)
|
||||
if options[:outdated]
|
||||
Bundler.ui.info "Fetching remote specs for outdated check...\n\n"
|
||||
Bundler.ui.silence { definition.resolve_remotely! }
|
||||
else
|
||||
definition.resolve_with_cache!
|
||||
end
|
||||
Bundler.reset!
|
||||
definition.specs
|
||||
end
|
||||
|
||||
def outdated?(current, latest)
|
||||
return false unless latest
|
||||
Gem::Version.new(current.version) < Gem::Version.new(latest.version)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Update
|
||||
attr_reader :options, :gems
|
||||
def initialize(options, gems)
|
||||
@options = options
|
||||
@gems = gems
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "error" if options[:quiet]
|
||||
|
||||
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
|
||||
|
||||
sources = Array(options[:source])
|
||||
groups = Array(options[:group]).map(&:to_sym)
|
||||
|
||||
full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !options[:bundler]
|
||||
|
||||
if full_update && !options[:all]
|
||||
if Bundler.feature_flag.update_requires_all_flag?
|
||||
raise InvalidOption, "To update everything, pass the `--all` flag."
|
||||
end
|
||||
SharedHelpers.major_deprecation 2, "Pass --all to `bundle update` to update everything"
|
||||
elsif !full_update && options[:all]
|
||||
raise InvalidOption, "Cannot specify --all along with specific options."
|
||||
end
|
||||
|
||||
if full_update
|
||||
# We're doing a full update
|
||||
Bundler.definition(true)
|
||||
else
|
||||
unless Bundler.default_lockfile.exist?
|
||||
raise GemfileLockNotFound, "This Bundle hasn't been installed yet. " \
|
||||
"Run `bundle install` to update and install the bundled gems."
|
||||
end
|
||||
Bundler::CLI::Common.ensure_all_gems_in_lockfile!(gems)
|
||||
|
||||
if groups.any?
|
||||
deps = Bundler.definition.dependencies.select {|d| (d.groups & groups).any? }
|
||||
gems.concat(deps.map(&:name))
|
||||
end
|
||||
|
||||
Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby],
|
||||
:lock_shared_dependencies => options[:conservative],
|
||||
:bundler => options[:bundler])
|
||||
end
|
||||
|
||||
Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options)
|
||||
|
||||
Bundler::Fetcher.disable_endpoint = options["full-index"]
|
||||
|
||||
opts = options.dup
|
||||
opts["update"] = true
|
||||
opts["local"] = options[:local]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
|
||||
|
||||
Bundler.definition.validate_runtime!
|
||||
installer = Installer.install Bundler.root, Bundler.definition, opts
|
||||
Bundler.load.cache if Bundler.app_cache.exist?
|
||||
|
||||
if CLI::Common.clean_after_install?
|
||||
require "bundler/cli/clean"
|
||||
Bundler::CLI::Clean.new(options).run
|
||||
end
|
||||
|
||||
if locked_gems = Bundler.definition.locked_gems
|
||||
gems.each do |name|
|
||||
locked_version = locked_gems.specs.find {|s| s.name == name }
|
||||
locked_version &&= locked_version.version
|
||||
next unless locked_version
|
||||
new_version = Bundler.definition.specs[name].first
|
||||
new_version &&= new_version.version
|
||||
if !new_version
|
||||
Bundler.ui.warn "Bundler attempted to update #{name} but it was removed from the bundle"
|
||||
elsif new_version < locked_version
|
||||
Bundler.ui.warn "Note: #{name} version regressed from #{locked_version} to #{new_version}"
|
||||
elsif new_version == locked_version
|
||||
Bundler.ui.warn "Bundler attempted to update #{name} but its version stayed the same"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Bundler.ui.confirm "Bundle updated!"
|
||||
Bundler::CLI::Common.output_without_groups_message
|
||||
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Viz
|
||||
attr_reader :options, :gem_name
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
# make sure we get the right `graphviz`. There is also a `graphviz`
|
||||
# gem we're not built to support
|
||||
gem "ruby-graphviz"
|
||||
require "graphviz"
|
||||
|
||||
options[:without] = options[:without].join(":").tr(" ", ":").split(":")
|
||||
output_file = File.expand_path(options[:file])
|
||||
|
||||
graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without])
|
||||
graph.viz
|
||||
rescue LoadError => e
|
||||
Bundler.ui.error e.inspect
|
||||
Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:"
|
||||
Bundler.ui.warn "`gem install ruby-graphviz`"
|
||||
rescue StandardError => e
|
||||
raise unless e.message =~ /GraphViz not installed or dot not in PATH/
|
||||
Bundler.ui.error e.message
|
||||
Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`."
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,109 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "pathname"
|
||||
require "set"
|
||||
|
||||
module Bundler
|
||||
class CompactIndexClient
|
||||
DEBUG_MUTEX = Mutex.new
|
||||
def self.debug
|
||||
return unless ENV["DEBUG_COMPACT_INDEX"]
|
||||
DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
|
||||
end
|
||||
|
||||
class Error < StandardError; end
|
||||
|
||||
require "bundler/compact_index_client/cache"
|
||||
require "bundler/compact_index_client/updater"
|
||||
|
||||
attr_reader :directory
|
||||
|
||||
# @return [Lambda] A lambda that takes an array of inputs and a block, and
|
||||
# maps the inputs with the block in parallel.
|
||||
#
|
||||
attr_accessor :in_parallel
|
||||
|
||||
def initialize(directory, fetcher)
|
||||
@directory = Pathname.new(directory)
|
||||
@updater = Updater.new(fetcher)
|
||||
@cache = Cache.new(@directory)
|
||||
@endpoints = Set.new
|
||||
@info_checksums_by_name = {}
|
||||
@parsed_checksums = false
|
||||
@mutex = Mutex.new
|
||||
@in_parallel = lambda do |inputs, &blk|
|
||||
inputs.map(&blk)
|
||||
end
|
||||
end
|
||||
|
||||
def names
|
||||
Bundler::CompactIndexClient.debug { "/names" }
|
||||
update(@cache.names_path, "names")
|
||||
@cache.names
|
||||
end
|
||||
|
||||
def versions
|
||||
Bundler::CompactIndexClient.debug { "/versions" }
|
||||
update(@cache.versions_path, "versions")
|
||||
versions, @info_checksums_by_name = @cache.versions
|
||||
versions
|
||||
end
|
||||
|
||||
def dependencies(names)
|
||||
Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
|
||||
in_parallel.call(names) do |name|
|
||||
update_info(name)
|
||||
@cache.dependencies(name).map {|d| d.unshift(name) }
|
||||
end.flatten(1)
|
||||
end
|
||||
|
||||
def spec(name, version, platform = nil)
|
||||
Bundler::CompactIndexClient.debug { "spec(name = #{name}, version = #{version}, platform = #{platform})" }
|
||||
update_info(name)
|
||||
@cache.specific_dependency(name, version, platform)
|
||||
end
|
||||
|
||||
def update_and_parse_checksums!
|
||||
Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" }
|
||||
return @info_checksums_by_name if @parsed_checksums
|
||||
update(@cache.versions_path, "versions")
|
||||
@info_checksums_by_name = @cache.checksums
|
||||
@parsed_checksums = true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update(local_path, remote_path)
|
||||
Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" }
|
||||
unless synchronize { @endpoints.add?(remote_path) }
|
||||
Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
|
||||
return
|
||||
end
|
||||
@updater.update(local_path, url(remote_path))
|
||||
end
|
||||
|
||||
def update_info(name)
|
||||
Bundler::CompactIndexClient.debug { "update_info(#{name})" }
|
||||
path = @cache.info_path(name)
|
||||
checksum = @updater.checksum_for_file(path)
|
||||
unless existing = @info_checksums_by_name[name]
|
||||
Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" }
|
||||
return
|
||||
end
|
||||
if checksum == existing
|
||||
Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" }
|
||||
return
|
||||
end
|
||||
Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" }
|
||||
update(path, "info/#{name}")
|
||||
end
|
||||
|
||||
def url(path)
|
||||
path
|
||||
end
|
||||
|
||||
def synchronize
|
||||
@mutex.synchronize { yield }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,118 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CompactIndexClient
|
||||
class Cache
|
||||
attr_reader :directory
|
||||
|
||||
def initialize(directory)
|
||||
@directory = Pathname.new(directory).expand_path
|
||||
info_roots.each do |dir|
|
||||
SharedHelpers.filesystem_access(dir) do
|
||||
FileUtils.mkdir_p(dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def names
|
||||
lines(names_path)
|
||||
end
|
||||
|
||||
def names_path
|
||||
directory.join("names")
|
||||
end
|
||||
|
||||
def versions
|
||||
versions_by_name = Hash.new {|hash, key| hash[key] = [] }
|
||||
info_checksums_by_name = {}
|
||||
|
||||
lines(versions_path).each do |line|
|
||||
name, versions_string, info_checksum = line.split(" ", 3)
|
||||
info_checksums_by_name[name] = info_checksum || ""
|
||||
versions_string.split(",").each do |version|
|
||||
if version.start_with?("-")
|
||||
version = version[1..-1].split("-", 2).unshift(name)
|
||||
versions_by_name[name].delete(version)
|
||||
else
|
||||
version = version.split("-", 2).unshift(name)
|
||||
versions_by_name[name] << version
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
[versions_by_name, info_checksums_by_name]
|
||||
end
|
||||
|
||||
def versions_path
|
||||
directory.join("versions")
|
||||
end
|
||||
|
||||
def checksums
|
||||
checksums = {}
|
||||
|
||||
lines(versions_path).each do |line|
|
||||
name, _, checksum = line.split(" ", 3)
|
||||
checksums[name] = checksum
|
||||
end
|
||||
|
||||
checksums
|
||||
end
|
||||
|
||||
def dependencies(name)
|
||||
lines(info_path(name)).map do |line|
|
||||
parse_gem(line)
|
||||
end
|
||||
end
|
||||
|
||||
def info_path(name)
|
||||
name = name.to_s
|
||||
if name =~ /[^a-z0-9_-]/
|
||||
name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}"
|
||||
info_roots.last.join(name)
|
||||
else
|
||||
info_roots.first.join(name)
|
||||
end
|
||||
end
|
||||
|
||||
def specific_dependency(name, version, platform)
|
||||
pattern = [version, platform].compact.join("-")
|
||||
return nil if pattern.empty?
|
||||
|
||||
gem_lines = info_path(name).read
|
||||
gem_line = gem_lines[/^#{Regexp.escape(pattern)}\b.*/, 0]
|
||||
gem_line ? parse_gem(gem_line) : nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lines(path)
|
||||
return [] unless path.file?
|
||||
lines = SharedHelpers.filesystem_access(path, :read, &:read).split("\n")
|
||||
header = lines.index("---")
|
||||
header ? lines[header + 1..-1] : lines
|
||||
end
|
||||
|
||||
def parse_gem(string)
|
||||
version_and_platform, rest = string.split(" ", 2)
|
||||
version, platform = version_and_platform.split("-", 2)
|
||||
dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
|
||||
dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
|
||||
requirements = requirements ? requirements.map {|r| parse_dependency(r) } : []
|
||||
[version, platform, dependencies, requirements]
|
||||
end
|
||||
|
||||
def parse_dependency(string)
|
||||
dependency = string.split(":")
|
||||
dependency[-1] = dependency[-1].split("&") if dependency.size > 1
|
||||
dependency
|
||||
end
|
||||
|
||||
def info_roots
|
||||
[
|
||||
directory.join("info"),
|
||||
directory.join("info-special-characters"),
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,116 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/vendored_fileutils"
|
||||
require "stringio"
|
||||
require "zlib"
|
||||
|
||||
module Bundler
|
||||
class CompactIndexClient
|
||||
class Updater
|
||||
class MisMatchedChecksumError < Error
|
||||
def initialize(path, server_checksum, local_checksum)
|
||||
@path = path
|
||||
@server_checksum = server_checksum
|
||||
@local_checksum = local_checksum
|
||||
end
|
||||
|
||||
def message
|
||||
"The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \
|
||||
"(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})."
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(fetcher)
|
||||
@fetcher = fetcher
|
||||
require "tmpdir"
|
||||
end
|
||||
|
||||
def update(local_path, remote_path, retrying = nil)
|
||||
headers = {}
|
||||
|
||||
Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir|
|
||||
local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename)
|
||||
|
||||
# first try to fetch any new bytes on the existing file
|
||||
if retrying.nil? && local_path.file?
|
||||
SharedHelpers.filesystem_access(local_temp_path) do
|
||||
FileUtils.cp local_path, local_temp_path
|
||||
end
|
||||
headers["If-None-Match"] = etag_for(local_temp_path)
|
||||
headers["Range"] =
|
||||
if local_temp_path.size.nonzero?
|
||||
# Subtract a byte to ensure the range won't be empty.
|
||||
# Avoids 416 (Range Not Satisfiable) responses.
|
||||
"bytes=#{local_temp_path.size - 1}-"
|
||||
else
|
||||
"bytes=#{local_temp_path.size}-"
|
||||
end
|
||||
else
|
||||
# Fastly ignores Range when Accept-Encoding: gzip is set
|
||||
headers["Accept-Encoding"] = "gzip"
|
||||
end
|
||||
|
||||
response = @fetcher.call(remote_path, headers)
|
||||
return nil if response.is_a?(Net::HTTPNotModified)
|
||||
|
||||
content = response.body
|
||||
if response["Content-Encoding"] == "gzip"
|
||||
content = Zlib::GzipReader.new(StringIO.new(content)).read
|
||||
end
|
||||
|
||||
SharedHelpers.filesystem_access(local_temp_path) do
|
||||
if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero?
|
||||
local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) }
|
||||
else
|
||||
local_temp_path.open("w") {|f| f << content }
|
||||
end
|
||||
end
|
||||
|
||||
response_etag = (response["ETag"] || "").gsub(%r{\AW/}, "")
|
||||
if etag_for(local_temp_path) == response_etag
|
||||
SharedHelpers.filesystem_access(local_path) do
|
||||
FileUtils.mv(local_temp_path, local_path)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if retrying
|
||||
raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path))
|
||||
end
|
||||
|
||||
update(local_path, remote_path, :retrying)
|
||||
end
|
||||
rescue Errno::EACCES
|
||||
raise Bundler::PermissionError,
|
||||
"Bundler does not have write access to create a temp directory " \
|
||||
"within #{Dir.tmpdir}. Bundler must have write access to your " \
|
||||
"systems temp directory to function properly. "
|
||||
rescue Zlib::GzipFile::Error
|
||||
raise Bundler::HTTPError
|
||||
end
|
||||
|
||||
def etag_for(path)
|
||||
sum = checksum_for_file(path)
|
||||
sum ? %("#{sum}") : nil
|
||||
end
|
||||
|
||||
def slice_body(body, range)
|
||||
if body.respond_to?(:byteslice)
|
||||
body.byteslice(range)
|
||||
else # pre-1.9.3
|
||||
body.unpack("@#{range.first}a#{range.end + 1}").first
|
||||
end
|
||||
end
|
||||
|
||||
def checksum_for_file(path)
|
||||
return nil unless path.file?
|
||||
# This must use IO.read instead of Digest.file().hexdigest
|
||||
# because we need to preserve \n line endings on windows when calculating
|
||||
# the checksum
|
||||
SharedHelpers.filesystem_access(path, :read) do
|
||||
SharedHelpers.digest(:MD5).hexdigest(IO.read(path))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: false
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/version"
|
||||
|
||||
if Bundler::VERSION.split(".").first.to_i >= 2
|
||||
if Gem::Version.new(Object::RUBY_VERSION.dup) < Gem::Version.new("2.3")
|
||||
abort "Bundler 2 requires Ruby 2.3 or later. Either install bundler 1 or update to a supported Ruby version."
|
||||
end
|
||||
|
||||
if Gem::Version.new(Gem::VERSION.dup) < Gem::Version.new("2.5")
|
||||
abort "Bundler 2 requires RubyGems 2.5 or later. Either install bundler 1 or update to a supported RubyGems version."
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
WINDOWS = RbConfig::CONFIG["host_os"] =~ /(msdos|mswin|djgpp|mingw)/
|
||||
FREEBSD = RbConfig::CONFIG["host_os"] =~ /bsd/
|
||||
NULL = WINDOWS ? "NUL" : "/dev/null"
|
||||
end
|
|
@ -0,0 +1,93 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# Returns current version of Ruby
|
||||
#
|
||||
# @return [CurrentRuby] Current version of Ruby
|
||||
def self.current_ruby
|
||||
@current_ruby ||= CurrentRuby.new
|
||||
end
|
||||
|
||||
class CurrentRuby
|
||||
KNOWN_MINOR_VERSIONS = %w[
|
||||
1.8
|
||||
1.9
|
||||
2.0
|
||||
2.1
|
||||
2.2
|
||||
2.3
|
||||
2.4
|
||||
2.5
|
||||
2.6
|
||||
].freeze
|
||||
|
||||
KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze
|
||||
|
||||
KNOWN_PLATFORMS = %w[
|
||||
jruby
|
||||
maglev
|
||||
mingw
|
||||
mri
|
||||
mswin
|
||||
mswin64
|
||||
rbx
|
||||
ruby
|
||||
truffleruby
|
||||
x64_mingw
|
||||
].freeze
|
||||
|
||||
def ruby?
|
||||
!mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" ||
|
||||
RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev" || RUBY_ENGINE == "truffleruby")
|
||||
end
|
||||
|
||||
def mri?
|
||||
!mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby")
|
||||
end
|
||||
|
||||
def rbx?
|
||||
ruby? && defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
|
||||
end
|
||||
|
||||
def jruby?
|
||||
defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
||||
end
|
||||
|
||||
def maglev?
|
||||
defined?(RUBY_ENGINE) && RUBY_ENGINE == "maglev"
|
||||
end
|
||||
|
||||
def truffleruby?
|
||||
defined?(RUBY_ENGINE) && RUBY_ENGINE == "truffleruby"
|
||||
end
|
||||
|
||||
def mswin?
|
||||
Bundler::WINDOWS
|
||||
end
|
||||
|
||||
def mswin64?
|
||||
Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64"
|
||||
end
|
||||
|
||||
def mingw?
|
||||
Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64"
|
||||
end
|
||||
|
||||
def x64_mingw?
|
||||
Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64"
|
||||
end
|
||||
|
||||
(KNOWN_MINOR_VERSIONS + KNOWN_MAJOR_VERSIONS).each do |version|
|
||||
trimmed_version = version.tr(".", "")
|
||||
define_method(:"on_#{trimmed_version}?") do
|
||||
RUBY_VERSION.start_with?("#{version}.")
|
||||
end
|
||||
|
||||
KNOWN_PLATFORMS.each do |platform|
|
||||
define_method(:"#{platform}_#{trimmed_version}?") do
|
||||
send(:"#{platform}?") && send(:"on_#{trimmed_version}?")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,993 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/lockfile_parser"
|
||||
require "set"
|
||||
|
||||
module Bundler
|
||||
class Definition
|
||||
include GemHelpers
|
||||
|
||||
attr_reader(
|
||||
:dependencies,
|
||||
:locked_deps,
|
||||
:locked_gems,
|
||||
:platforms,
|
||||
:requires,
|
||||
:ruby_version,
|
||||
:lockfile,
|
||||
:gemfiles
|
||||
)
|
||||
|
||||
# Given a gemfile and lockfile creates a Bundler definition
|
||||
#
|
||||
# @param gemfile [Pathname] Path to Gemfile
|
||||
# @param lockfile [Pathname,nil] Path to Gemfile.lock
|
||||
# @param unlock [Hash, Boolean, nil] Gems that have been requested
|
||||
# to be updated or true if all gems should be updated
|
||||
# @return [Bundler::Definition]
|
||||
def self.build(gemfile, lockfile, unlock)
|
||||
unlock ||= {}
|
||||
gemfile = Pathname.new(gemfile).expand_path
|
||||
|
||||
raise GemfileNotFound, "#{gemfile} not found" unless gemfile.file?
|
||||
|
||||
Dsl.evaluate(gemfile, lockfile, unlock)
|
||||
end
|
||||
|
||||
#
|
||||
# How does the new system work?
|
||||
#
|
||||
# * Load information from Gemfile and Lockfile
|
||||
# * Invalidate stale locked specs
|
||||
# * All specs from stale source are stale
|
||||
# * All specs that are reachable only through a stale
|
||||
# dependency are stale.
|
||||
# * If all fresh dependencies are satisfied by the locked
|
||||
# specs, then we can try to resolve locally.
|
||||
#
|
||||
# @param lockfile [Pathname] Path to Gemfile.lock
|
||||
# @param dependencies [Array(Bundler::Dependency)] array of dependencies from Gemfile
|
||||
# @param sources [Bundler::SourceList]
|
||||
# @param unlock [Hash, Boolean, nil] Gems that have been requested
|
||||
# to be updated or true if all gems should be updated
|
||||
# @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
|
||||
# @param optional_groups [Array(String)] A list of optional groups
|
||||
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [])
|
||||
if [true, false].include?(unlock)
|
||||
@unlocking_bundler = false
|
||||
@unlocking = unlock
|
||||
else
|
||||
unlock = unlock.dup
|
||||
@unlocking_bundler = unlock.delete(:bundler)
|
||||
unlock.delete_if {|_k, v| Array(v).empty? }
|
||||
@unlocking = !unlock.empty?
|
||||
end
|
||||
|
||||
@dependencies = dependencies
|
||||
@sources = sources
|
||||
@unlock = unlock
|
||||
@optional_groups = optional_groups
|
||||
@remote = false
|
||||
@specs = nil
|
||||
@ruby_version = ruby_version
|
||||
@gemfiles = gemfiles
|
||||
|
||||
@lockfile = lockfile
|
||||
@lockfile_contents = String.new
|
||||
@locked_bundler_version = nil
|
||||
@locked_ruby_version = nil
|
||||
@locked_specs_incomplete_for_platform = false
|
||||
|
||||
if lockfile && File.exist?(lockfile)
|
||||
@lockfile_contents = Bundler.read_file(lockfile)
|
||||
@locked_gems = LockfileParser.new(@lockfile_contents)
|
||||
@locked_platforms = @locked_gems.platforms
|
||||
@platforms = @locked_platforms.dup
|
||||
@locked_bundler_version = @locked_gems.bundler_version
|
||||
@locked_ruby_version = @locked_gems.ruby_version
|
||||
|
||||
if unlock != true
|
||||
@locked_deps = @locked_gems.dependencies
|
||||
@locked_specs = SpecSet.new(@locked_gems.specs)
|
||||
@locked_sources = @locked_gems.sources
|
||||
else
|
||||
@unlock = {}
|
||||
@locked_deps = {}
|
||||
@locked_specs = SpecSet.new([])
|
||||
@locked_sources = []
|
||||
end
|
||||
else
|
||||
@unlock = {}
|
||||
@platforms = []
|
||||
@locked_gems = nil
|
||||
@locked_deps = {}
|
||||
@locked_specs = SpecSet.new([])
|
||||
@locked_sources = []
|
||||
@locked_platforms = []
|
||||
end
|
||||
|
||||
@unlock[:gems] ||= []
|
||||
@unlock[:sources] ||= []
|
||||
@unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
|
||||
@ruby_version.diff(locked_ruby_version_object)
|
||||
end
|
||||
@unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)
|
||||
|
||||
add_current_platform unless Bundler.frozen_bundle?
|
||||
|
||||
converge_path_sources_to_gemspec_sources
|
||||
@path_changes = converge_paths
|
||||
@source_changes = converge_sources
|
||||
|
||||
unless @unlock[:lock_shared_dependencies]
|
||||
eager_unlock = expand_dependencies(@unlock[:gems], true)
|
||||
@unlock[:gems] = @locked_specs.for(eager_unlock, [], false, false, false).map(&:name)
|
||||
end
|
||||
|
||||
@dependency_changes = converge_dependencies
|
||||
@local_changes = converge_locals
|
||||
|
||||
@requires = compute_requires
|
||||
end
|
||||
|
||||
def gem_version_promoter
|
||||
@gem_version_promoter ||= begin
|
||||
locked_specs =
|
||||
if unlocking? && @locked_specs.empty? && !@lockfile_contents.empty?
|
||||
# Definition uses an empty set of locked_specs to indicate all gems
|
||||
# are unlocked, but GemVersionPromoter needs the locked_specs
|
||||
# for conservative comparison.
|
||||
Bundler::SpecSet.new(@locked_gems.specs)
|
||||
else
|
||||
@locked_specs
|
||||
end
|
||||
GemVersionPromoter.new(locked_specs, @unlock[:gems])
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_with_cache!
|
||||
raise "Specs already loaded" if @specs
|
||||
sources.cached!
|
||||
specs
|
||||
end
|
||||
|
||||
def resolve_remotely!
|
||||
raise "Specs already loaded" if @specs
|
||||
@remote = true
|
||||
sources.remote!
|
||||
specs
|
||||
end
|
||||
|
||||
# For given dependency list returns a SpecSet with Gemspec of all the required
|
||||
# dependencies.
|
||||
# 1. The method first resolves the dependencies specified in Gemfile
|
||||
# 2. After that it tries and fetches gemspec of resolved dependencies
|
||||
#
|
||||
# @return [Bundler::SpecSet]
|
||||
def specs
|
||||
@specs ||= begin
|
||||
begin
|
||||
specs = resolve.materialize(Bundler.settings[:cache_all_platforms] ? dependencies : requested_dependencies)
|
||||
rescue GemNotFound => e # Handle yanked gem
|
||||
gem_name, gem_version = extract_gem_info(e)
|
||||
locked_gem = @locked_specs[gem_name].last
|
||||
raise if locked_gem.nil? || locked_gem.version.to_s != gem_version || !@remote
|
||||
raise GemNotFound, "Your bundle is locked to #{locked_gem}, but that version could not " \
|
||||
"be found in any of the sources listed in your Gemfile. If you haven't changed sources, " \
|
||||
"that means the author of #{locked_gem} has removed it. You'll need to update your bundle " \
|
||||
"to a version other than #{locked_gem} that hasn't been removed in order to install."
|
||||
end
|
||||
unless specs["bundler"].any?
|
||||
bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last
|
||||
specs["bundler"] = bundler
|
||||
end
|
||||
|
||||
specs
|
||||
end
|
||||
end
|
||||
|
||||
def new_specs
|
||||
specs - @locked_specs
|
||||
end
|
||||
|
||||
def removed_specs
|
||||
@locked_specs - specs
|
||||
end
|
||||
|
||||
def new_platform?
|
||||
@new_platform
|
||||
end
|
||||
|
||||
def missing_specs
|
||||
missing = []
|
||||
resolve.materialize(requested_dependencies, missing)
|
||||
missing
|
||||
end
|
||||
|
||||
def missing_specs?
|
||||
missing = missing_specs
|
||||
return false if missing.empty?
|
||||
Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}"
|
||||
true
|
||||
rescue BundlerError => e
|
||||
@index = nil
|
||||
@resolve = nil
|
||||
@specs = nil
|
||||
@gem_version_promoter = nil
|
||||
|
||||
Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
|
||||
true
|
||||
end
|
||||
|
||||
def requested_specs
|
||||
@requested_specs ||= begin
|
||||
groups = requested_groups
|
||||
groups.map!(&:to_sym)
|
||||
specs_for(groups)
|
||||
end
|
||||
end
|
||||
|
||||
def current_dependencies
|
||||
dependencies.select(&:should_include?)
|
||||
end
|
||||
|
||||
def specs_for(groups)
|
||||
deps = dependencies.select {|d| (d.groups & groups).any? }
|
||||
deps.delete_if {|d| !d.should_include? }
|
||||
specs.for(expand_dependencies(deps))
|
||||
end
|
||||
|
||||
# Resolve all the dependencies specified in Gemfile. It ensures that
|
||||
# dependencies that have been already resolved via locked file and are fresh
|
||||
# are reused when resolving dependencies
|
||||
#
|
||||
# @return [SpecSet] resolved dependencies
|
||||
def resolve
|
||||
@resolve ||= begin
|
||||
last_resolve = converge_locked_specs
|
||||
resolve =
|
||||
if Bundler.frozen_bundle?
|
||||
Bundler.ui.debug "Frozen, using resolution from the lockfile"
|
||||
last_resolve
|
||||
elsif !unlocking? && nothing_changed?
|
||||
Bundler.ui.debug("Found no changes, using resolution from the lockfile")
|
||||
last_resolve
|
||||
else
|
||||
# Run a resolve against the locally available gems
|
||||
Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
|
||||
last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
|
||||
end
|
||||
|
||||
# filter out gems that _can_ be installed on multiple platforms, but don't need
|
||||
# to be
|
||||
resolve.for(expand_dependencies(dependencies, true), [], false, false, false)
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
@index ||= Index.build do |idx|
|
||||
dependency_names = @dependencies.map(&:name)
|
||||
|
||||
sources.all_sources.each do |source|
|
||||
source.dependency_names = dependency_names - pinned_spec_names(source)
|
||||
idx.add_source source.specs
|
||||
dependency_names.concat(source.unmet_deps).uniq!
|
||||
end
|
||||
|
||||
double_check_for_index(idx, dependency_names)
|
||||
end
|
||||
end
|
||||
|
||||
# Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both
|
||||
# sources A and B. At this point, the API request will have found all the versions of Bar in source A,
|
||||
# but will not have found any versions of Bar from source B, which is a problem if the requested version
|
||||
# of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for
|
||||
# each spec we found, we add all possible versions from all sources to the index.
|
||||
def double_check_for_index(idx, dependency_names)
|
||||
pinned_names = pinned_spec_names
|
||||
loop do
|
||||
idxcount = idx.size
|
||||
|
||||
names = :names # do this so we only have to traverse to get dependency_names from the index once
|
||||
unmet_dependency_names = lambda do
|
||||
return names unless names == :names
|
||||
new_names = sources.all_sources.map(&:dependency_names_to_double_check)
|
||||
return names = nil if new_names.compact!
|
||||
names = new_names.flatten(1).concat(dependency_names)
|
||||
names.uniq!
|
||||
names -= pinned_names
|
||||
names
|
||||
end
|
||||
|
||||
sources.all_sources.each do |source|
|
||||
source.double_check_for(unmet_dependency_names)
|
||||
end
|
||||
|
||||
break if idxcount == idx.size
|
||||
end
|
||||
end
|
||||
private :double_check_for_index
|
||||
|
||||
def has_rubygems_remotes?
|
||||
sources.rubygems_sources.any? {|s| s.remotes.any? }
|
||||
end
|
||||
|
||||
def has_local_dependencies?
|
||||
!sources.path_sources.empty? || !sources.git_sources.empty?
|
||||
end
|
||||
|
||||
def spec_git_paths
|
||||
sources.git_sources.map {|s| s.path.to_s }
|
||||
end
|
||||
|
||||
def groups
|
||||
dependencies.map(&:groups).flatten.uniq
|
||||
end
|
||||
|
||||
def lock(file, preserve_unknown_sections = false)
|
||||
contents = to_lock
|
||||
|
||||
# Convert to \r\n if the existing lock has them
|
||||
# i.e., Windows with `git config core.autocrlf=true`
|
||||
contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match("\r\n")
|
||||
|
||||
if @locked_bundler_version
|
||||
locked_major = @locked_bundler_version.segments.first
|
||||
current_major = Gem::Version.create(Bundler::VERSION).segments.first
|
||||
|
||||
if updating_major = locked_major < current_major
|
||||
Bundler.ui.warn "Warning: the lockfile is being updated to Bundler #{current_major}, " \
|
||||
"after which you will be unable to return to Bundler #{@locked_bundler_version.segments.first}."
|
||||
end
|
||||
end
|
||||
|
||||
preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler))
|
||||
|
||||
return if file && File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)
|
||||
|
||||
if Bundler.frozen_bundle?
|
||||
Bundler.ui.error "Cannot write a changed lockfile while frozen."
|
||||
return
|
||||
end
|
||||
|
||||
SharedHelpers.filesystem_access(file) do |p|
|
||||
File.open(p, "wb") {|f| f.puts(contents) }
|
||||
end
|
||||
end
|
||||
|
||||
def locked_bundler_version
|
||||
if @locked_bundler_version && @locked_bundler_version < Gem::Version.new(Bundler::VERSION)
|
||||
new_version = Bundler::VERSION
|
||||
end
|
||||
|
||||
new_version || @locked_bundler_version || Bundler::VERSION
|
||||
end
|
||||
|
||||
def locked_ruby_version
|
||||
return unless ruby_version
|
||||
if @unlock[:ruby] || !@locked_ruby_version
|
||||
Bundler::RubyVersion.system
|
||||
else
|
||||
@locked_ruby_version
|
||||
end
|
||||
end
|
||||
|
||||
def locked_ruby_version_object
|
||||
return unless @locked_ruby_version
|
||||
@locked_ruby_version_object ||= begin
|
||||
unless version = RubyVersion.from_string(@locked_ruby_version)
|
||||
raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \
|
||||
"#{@lockfile} could not be parsed. " \
|
||||
"Try running bundle update --ruby to resolve this."
|
||||
end
|
||||
version
|
||||
end
|
||||
end
|
||||
|
||||
def to_lock
|
||||
require "bundler/lockfile_generator"
|
||||
LockfileGenerator.generate(self)
|
||||
end
|
||||
|
||||
def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
|
||||
msg = String.new
|
||||
msg << "You are trying to install in deployment mode after changing\n" \
|
||||
"your Gemfile. Run `bundle install` elsewhere and add the\n" \
|
||||
"updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control."
|
||||
|
||||
unless explicit_flag
|
||||
suggested_command = if Bundler.settings.locations("frozen")[:global]
|
||||
"bundle config --delete frozen"
|
||||
elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
|
||||
"bundle config --delete deployment"
|
||||
else
|
||||
"bundle install --no-deployment"
|
||||
end
|
||||
msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \
|
||||
"freeze \nby running `#{suggested_command}`."
|
||||
end
|
||||
|
||||
added = []
|
||||
deleted = []
|
||||
changed = []
|
||||
|
||||
new_platforms = @platforms - @locked_platforms
|
||||
deleted_platforms = @locked_platforms - @platforms
|
||||
added.concat new_platforms.map {|p| "* platform: #{p}" }
|
||||
deleted.concat deleted_platforms.map {|p| "* platform: #{p}" }
|
||||
|
||||
gemfile_sources = sources.lock_sources
|
||||
|
||||
new_sources = gemfile_sources - @locked_sources
|
||||
deleted_sources = @locked_sources - gemfile_sources
|
||||
|
||||
new_deps = @dependencies - @locked_deps.values
|
||||
deleted_deps = @locked_deps.values - @dependencies
|
||||
|
||||
# Check if it is possible that the source is only changed thing
|
||||
if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?)
|
||||
new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
|
||||
deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
|
||||
end
|
||||
|
||||
if @locked_sources != gemfile_sources
|
||||
if new_sources.any?
|
||||
added.concat new_sources.map {|source| "* source: #{source}" }
|
||||
end
|
||||
|
||||
if deleted_sources.any?
|
||||
deleted.concat deleted_sources.map {|source| "* source: #{source}" }
|
||||
end
|
||||
end
|
||||
|
||||
added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
|
||||
if deleted_deps.any?
|
||||
deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" }
|
||||
end
|
||||
|
||||
both_sources = Hash.new {|h, k| h[k] = [] }
|
||||
@dependencies.each {|d| both_sources[d.name][0] = d }
|
||||
@locked_deps.each {|name, d| both_sources[name][1] = d.source }
|
||||
|
||||
both_sources.each do |name, (dep, lock_source)|
|
||||
next unless (dep.nil? && !lock_source.nil?) || (!dep.nil? && !lock_source.nil? && !lock_source.can_lock?(dep))
|
||||
gemfile_source_name = (dep && dep.source) || "no specified source"
|
||||
lockfile_source_name = lock_source || "no specified source"
|
||||
changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`"
|
||||
end
|
||||
|
||||
reason = change_reason
|
||||
msg << "\n\n#{reason.split(", ").map(&:capitalize).join("\n")}" unless reason.strip.empty?
|
||||
msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
|
||||
msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
|
||||
msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any?
|
||||
msg << "\n"
|
||||
|
||||
raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed?
|
||||
end
|
||||
|
||||
def validate_runtime!
|
||||
validate_ruby!
|
||||
validate_platforms!
|
||||
end
|
||||
|
||||
def validate_ruby!
|
||||
return unless ruby_version
|
||||
|
||||
if diff = ruby_version.diff(Bundler::RubyVersion.system)
|
||||
problem, expected, actual = diff
|
||||
|
||||
msg = case problem
|
||||
when :engine
|
||||
"Your Ruby engine is #{actual}, but your Gemfile specified #{expected}"
|
||||
when :version
|
||||
"Your Ruby version is #{actual}, but your Gemfile specified #{expected}"
|
||||
when :engine_version
|
||||
"Your #{Bundler::RubyVersion.system.engine} version is #{actual}, but your Gemfile specified #{ruby_version.engine} #{expected}"
|
||||
when :patchlevel
|
||||
if !expected.is_a?(String)
|
||||
"The Ruby patchlevel in your Gemfile must be a string"
|
||||
else
|
||||
"Your Ruby patchlevel is #{actual}, but your Gemfile specified #{expected}"
|
||||
end
|
||||
end
|
||||
|
||||
raise RubyVersionMismatch, msg
|
||||
end
|
||||
end
|
||||
|
||||
def validate_platforms!
|
||||
return if @platforms.any? do |bundle_platform|
|
||||
Bundler.rubygems.platforms.any? do |local_platform|
|
||||
MatchPlatform.platforms_match?(bundle_platform, local_platform)
|
||||
end
|
||||
end
|
||||
|
||||
raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
|
||||
"but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
|
||||
"there's no compatible match between those two lists."
|
||||
end
|
||||
|
||||
def add_platform(platform)
|
||||
@new_platform ||= !@platforms.include?(platform)
|
||||
@platforms |= [platform]
|
||||
end
|
||||
|
||||
def remove_platform(platform)
|
||||
return if @platforms.delete(Gem::Platform.new(platform))
|
||||
raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
|
||||
end
|
||||
|
||||
def add_current_platform
|
||||
current_platform = Bundler.local_platform
|
||||
add_platform(current_platform) if Bundler.feature_flag.specific_platform?
|
||||
add_platform(generic(current_platform))
|
||||
end
|
||||
|
||||
def find_resolved_spec(current_spec)
|
||||
specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
|
||||
end
|
||||
|
||||
def find_indexed_specs(current_spec)
|
||||
index[current_spec.name].select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
|
||||
end
|
||||
|
||||
attr_reader :sources
|
||||
private :sources
|
||||
|
||||
def nothing_changed?
|
||||
!@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
|
||||
end
|
||||
|
||||
def unlocking?
|
||||
@unlocking
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def change_reason
|
||||
if unlocking?
|
||||
unlock_reason = @unlock.reject {|_k, v| Array(v).empty? }.map do |k, v|
|
||||
if v == true
|
||||
k.to_s
|
||||
else
|
||||
v = Array(v)
|
||||
"#{k}: (#{v.join(", ")})"
|
||||
end
|
||||
end.join(", ")
|
||||
return "bundler is unlocking #{unlock_reason}"
|
||||
end
|
||||
[
|
||||
[@source_changes, "the list of sources changed"],
|
||||
[@dependency_changes, "the dependencies in your gemfile changed"],
|
||||
[@new_platform, "you added a new platform to your gemfile"],
|
||||
[@path_changes, "the gemspecs for path gems changed"],
|
||||
[@local_changes, "the gemspecs for git local gems changed"],
|
||||
[@locked_specs_incomplete_for_platform, "the lockfile does not have all gems needed for the current platform"],
|
||||
].select(&:first).map(&:last).join(", ")
|
||||
end
|
||||
|
||||
def pretty_dep(dep, source = false)
|
||||
SharedHelpers.pretty_dependency(dep, source)
|
||||
end
|
||||
|
||||
# Check if the specs of the given source changed
|
||||
# according to the locked source.
|
||||
def specs_changed?(source)
|
||||
locked = @locked_sources.find {|s| s == source }
|
||||
|
||||
!locked || dependencies_for_source_changed?(source, locked) || specs_for_source_changed?(source)
|
||||
end
|
||||
|
||||
def dependencies_for_source_changed?(source, locked_source = source)
|
||||
deps_for_source = @dependencies.select {|s| s.source == source }
|
||||
locked_deps_for_source = @locked_deps.values.select {|dep| dep.source == locked_source }
|
||||
|
||||
Set.new(deps_for_source) != Set.new(locked_deps_for_source)
|
||||
end
|
||||
|
||||
def specs_for_source_changed?(source)
|
||||
locked_index = Index.new
|
||||
locked_index.use(@locked_specs.select {|s| source.can_lock?(s) })
|
||||
|
||||
# order here matters, since Index#== is checking source.specs.include?(locked_index)
|
||||
locked_index != source.specs
|
||||
rescue PathError, GitError => e
|
||||
Bundler.ui.debug "Assuming that #{source} has not changed since fetching its specs errored (#{e})"
|
||||
false
|
||||
end
|
||||
|
||||
# Get all locals and override their matching sources.
|
||||
# Return true if any of the locals changed (for example,
|
||||
# they point to a new revision) or depend on new specs.
|
||||
def converge_locals
|
||||
locals = []
|
||||
|
||||
Bundler.settings.local_overrides.map do |k, v|
|
||||
spec = @dependencies.find {|s| s.name == k }
|
||||
source = spec && spec.source
|
||||
if source && source.respond_to?(:local_override!)
|
||||
source.unlock! if @unlock[:gems].include?(spec.name)
|
||||
locals << [source, source.local_override!(v)]
|
||||
end
|
||||
end
|
||||
|
||||
sources_with_changes = locals.select do |source, changed|
|
||||
changed || specs_changed?(source)
|
||||
end.map(&:first)
|
||||
!sources_with_changes.each {|source| @unlock[:sources] << source.name }.empty?
|
||||
end
|
||||
|
||||
def converge_paths
|
||||
sources.path_sources.any? do |source|
|
||||
specs_changed?(source)
|
||||
end
|
||||
end
|
||||
|
||||
def converge_path_source_to_gemspec_source(source)
|
||||
return source unless source.instance_of?(Source::Path)
|
||||
gemspec_source = sources.path_sources.find {|s| s.is_a?(Source::Gemspec) && s.as_path_source == source }
|
||||
gemspec_source || source
|
||||
end
|
||||
|
||||
def converge_path_sources_to_gemspec_sources
|
||||
@locked_sources.map! do |source|
|
||||
converge_path_source_to_gemspec_source(source)
|
||||
end
|
||||
@locked_specs.each do |spec|
|
||||
spec.source &&= converge_path_source_to_gemspec_source(spec.source)
|
||||
end
|
||||
@locked_deps.each do |_, dep|
|
||||
dep.source &&= converge_path_source_to_gemspec_source(dep.source)
|
||||
end
|
||||
end
|
||||
|
||||
def converge_rubygems_sources
|
||||
return false if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
|
||||
|
||||
changes = false
|
||||
|
||||
# Get the RubyGems sources from the Gemfile.lock
|
||||
locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
|
||||
# Get the RubyGems remotes from the Gemfile
|
||||
actual_remotes = sources.rubygems_remotes
|
||||
|
||||
# If there is a RubyGems source in both
|
||||
if !locked_gem_sources.empty? && !actual_remotes.empty?
|
||||
locked_gem_sources.each do |locked_gem|
|
||||
# Merge the remotes from the Gemfile into the Gemfile.lock
|
||||
changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
|
||||
end
|
||||
end
|
||||
|
||||
changes
|
||||
end
|
||||
|
||||
def converge_sources
|
||||
changes = false
|
||||
|
||||
changes |= converge_rubygems_sources
|
||||
|
||||
# Replace the sources from the Gemfile with the sources from the Gemfile.lock,
|
||||
# if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent
|
||||
# source in the Gemfile.lock, use the one from the Gemfile.
|
||||
changes |= sources.replace_sources!(@locked_sources)
|
||||
|
||||
sources.all_sources.each do |source|
|
||||
# If the source is unlockable and the current command allows an unlock of
|
||||
# the source (for example, you are doing a `bundle update <foo>` of a git-pinned
|
||||
# gem), unlock it. For git sources, this means to unlock the revision, which
|
||||
# will cause the `ref` used to be the most recent for the branch (or master) if
|
||||
# an explicit `ref` is not used.
|
||||
if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name)
|
||||
source.unlock!
|
||||
changes = true
|
||||
end
|
||||
end
|
||||
|
||||
changes
|
||||
end
|
||||
|
||||
def converge_dependencies
|
||||
frozen = Bundler.frozen_bundle?
|
||||
(@dependencies + @locked_deps.values).each do |dep|
|
||||
locked_source = @locked_deps[dep.name]
|
||||
# This is to make sure that if bundler is installing in deployment mode and
|
||||
# after locked_source and sources don't match, we still use locked_source.
|
||||
if frozen && !locked_source.nil? &&
|
||||
locked_source.respond_to?(:source) && locked_source.source.instance_of?(Source::Path) && locked_source.source.path.exist?
|
||||
dep.source = locked_source.source
|
||||
elsif dep.source
|
||||
dep.source = sources.get(dep.source)
|
||||
end
|
||||
if dep.source.is_a?(Source::Gemspec)
|
||||
dep.platforms.concat(@platforms.map {|p| Dependency::REVERSE_PLATFORM_MAP[p] }.flatten(1)).uniq!
|
||||
end
|
||||
end
|
||||
|
||||
changes = false
|
||||
# We want to know if all match, but don't want to check all entries
|
||||
# This means we need to return false if any dependency doesn't match
|
||||
# the lock or doesn't exist in the lock.
|
||||
@dependencies.each do |dependency|
|
||||
unless locked_dep = @locked_deps[dependency.name]
|
||||
changes = true
|
||||
next
|
||||
end
|
||||
|
||||
# Gem::Dependency#== matches Gem::Dependency#type. As the lockfile
|
||||
# doesn't carry a notion of the dependency type, if you use
|
||||
# add_development_dependency in a gemspec that's loaded with the gemspec
|
||||
# directive, the lockfile dependencies and resolved dependencies end up
|
||||
# with a mismatch on #type. Work around that by setting the type on the
|
||||
# dep from the lockfile.
|
||||
locked_dep.instance_variable_set(:@type, dependency.type)
|
||||
|
||||
# We already know the name matches from the hash lookup
|
||||
# so we only need to check the requirement now
|
||||
changes ||= dependency.requirement != locked_dep.requirement
|
||||
end
|
||||
|
||||
changes
|
||||
end
|
||||
|
||||
# Remove elements from the locked specs that are expired. This will most
|
||||
# commonly happen if the Gemfile has changed since the lockfile was last
|
||||
# generated
|
||||
def converge_locked_specs
|
||||
deps = []
|
||||
|
||||
# Build a list of dependencies that are the same in the Gemfile
|
||||
# and Gemfile.lock. If the Gemfile modified a dependency, but
|
||||
# the gem in the Gemfile.lock still satisfies it, this is fine
|
||||
# too.
|
||||
@dependencies.each do |dep|
|
||||
locked_dep = @locked_deps[dep.name]
|
||||
|
||||
# If the locked_dep doesn't match the dependency we're looking for then we ignore the locked_dep
|
||||
locked_dep = nil unless locked_dep == dep
|
||||
|
||||
if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep)
|
||||
deps << dep
|
||||
elsif dep.source.is_a?(Source::Path) && dep.current_platform? && (!locked_dep || dep.source != locked_dep.source)
|
||||
@locked_specs.each do |s|
|
||||
@unlock[:gems] << s.name if s.source == dep.source
|
||||
end
|
||||
|
||||
dep.source.unlock! if dep.source.respond_to?(:unlock!)
|
||||
dep.source.specs.each {|s| @unlock[:gems] << s.name }
|
||||
end
|
||||
end
|
||||
|
||||
unlock_source_unlocks_spec = Bundler.feature_flag.unlock_source_unlocks_spec?
|
||||
|
||||
converged = []
|
||||
@locked_specs.each do |s|
|
||||
# Replace the locked dependency's source with the equivalent source from the Gemfile
|
||||
dep = @dependencies.find {|d| s.satisfies?(d) }
|
||||
s.source = (dep && dep.source) || sources.get(s.source)
|
||||
|
||||
# Don't add a spec to the list if its source is expired. For example,
|
||||
# if you change a Git gem to RubyGems.
|
||||
next if s.source.nil?
|
||||
next if @unlock[:sources].include?(s.source.name)
|
||||
|
||||
# XXX This is a backwards-compatibility fix to preserve the ability to
|
||||
# unlock a single gem by passing its name via `--source`. See issue #3759
|
||||
# TODO: delete in Bundler 2
|
||||
next if unlock_source_unlocks_spec && @unlock[:sources].include?(s.name)
|
||||
|
||||
# If the spec is from a path source and it doesn't exist anymore
|
||||
# then we unlock it.
|
||||
|
||||
# Path sources have special logic
|
||||
if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec)
|
||||
other_sources_specs = begin
|
||||
s.source.specs
|
||||
rescue PathError, GitError
|
||||
# if we won't need the source (according to the lockfile),
|
||||
# don't error if the path/git source isn't available
|
||||
next if @locked_specs.
|
||||
for(requested_dependencies, [], false, true, false).
|
||||
none? {|locked_spec| locked_spec.source == s.source }
|
||||
|
||||
raise
|
||||
end
|
||||
|
||||
other = other_sources_specs[s].first
|
||||
|
||||
# If the spec is no longer in the path source, unlock it. This
|
||||
# commonly happens if the version changed in the gemspec
|
||||
next unless other
|
||||
|
||||
deps2 = other.dependencies.select {|d| d.type != :development }
|
||||
runtime_dependencies = s.dependencies.select {|d| d.type != :development }
|
||||
# If the dependencies of the path source have changed, unlock it
|
||||
next unless runtime_dependencies.sort == deps2.sort
|
||||
end
|
||||
|
||||
converged << s
|
||||
end
|
||||
|
||||
resolve = SpecSet.new(converged)
|
||||
expanded_deps = expand_dependencies(deps, true)
|
||||
@locked_specs_incomplete_for_platform = !resolve.for(expanded_deps, @unlock[:gems], true, true)
|
||||
resolve = resolve.for(expanded_deps, @unlock[:gems], false, false, false)
|
||||
diff = nil
|
||||
|
||||
# Now, we unlock any sources that do not have anymore gems pinned to it
|
||||
sources.all_sources.each do |source|
|
||||
next unless source.respond_to?(:unlock!)
|
||||
|
||||
unless resolve.any? {|s| s.source == source }
|
||||
diff ||= @locked_specs.to_a - resolve.to_a
|
||||
source.unlock! if diff.any? {|s| s.source == source }
|
||||
end
|
||||
end
|
||||
|
||||
resolve
|
||||
end
|
||||
|
||||
def in_locked_deps?(dep, locked_dep)
|
||||
# Because the lockfile can't link a dep to a specific remote, we need to
|
||||
# treat sources as equivalent anytime the locked dep has all the remotes
|
||||
# that the Gemfile dep does.
|
||||
locked_dep && locked_dep.source && dep.source && locked_dep.source.include?(dep.source)
|
||||
end
|
||||
|
||||
def satisfies_locked_spec?(dep)
|
||||
@locked_specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
|
||||
end
|
||||
|
||||
# This list of dependencies is only used in #resolve, so it's OK to add
|
||||
# the metadata dependencies here
|
||||
def expanded_dependencies
|
||||
@expanded_dependencies ||= begin
|
||||
expand_dependencies(dependencies + metadata_dependencies, @remote)
|
||||
end
|
||||
end
|
||||
|
||||
def metadata_dependencies
|
||||
@metadata_dependencies ||= begin
|
||||
ruby_versions = concat_ruby_version_requirements(@ruby_version)
|
||||
if ruby_versions.empty? || !@ruby_version.exact?
|
||||
concat_ruby_version_requirements(RubyVersion.system)
|
||||
concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby]
|
||||
end
|
||||
[
|
||||
Dependency.new("ruby\0", ruby_versions),
|
||||
Dependency.new("rubygems\0", Gem::VERSION),
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def concat_ruby_version_requirements(ruby_version, ruby_versions = [])
|
||||
return ruby_versions unless ruby_version
|
||||
if ruby_version.patchlevel
|
||||
ruby_versions << ruby_version.to_gem_version_with_patchlevel
|
||||
else
|
||||
ruby_versions.concat(ruby_version.versions.map do |version|
|
||||
requirement = Gem::Requirement.new(version)
|
||||
if requirement.exact?
|
||||
"~> #{version}.0"
|
||||
else
|
||||
requirement
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def expand_dependencies(dependencies, remote = false)
|
||||
sorted_platforms = Resolver.sort_platforms(@platforms)
|
||||
deps = []
|
||||
dependencies.each do |dep|
|
||||
dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
|
||||
next if !remote && !dep.current_platform?
|
||||
platforms = dep.gem_platforms(sorted_platforms)
|
||||
if platforms.empty? && !Bundler.settings[:disable_platform_warnings]
|
||||
mapped_platforms = dep.platforms.map {|p| Dependency::PLATFORM_MAP[p] }
|
||||
Bundler.ui.warn \
|
||||
"The dependency #{dep} will be unused by any of the platforms Bundler is installing for. " \
|
||||
"Bundler is installing for #{@platforms.join ", "} but the dependency " \
|
||||
"is only for #{mapped_platforms.join ", "}. " \
|
||||
"To add those platforms to the bundle, " \
|
||||
"run `bundle lock --add-platform #{mapped_platforms.join " "}`."
|
||||
end
|
||||
platforms.each do |p|
|
||||
deps << DepProxy.new(dep, p) if remote || p == generic_local_platform
|
||||
end
|
||||
end
|
||||
deps
|
||||
end
|
||||
|
||||
def requested_dependencies
|
||||
groups = requested_groups
|
||||
groups.map!(&:to_sym)
|
||||
dependencies.reject {|d| !d.should_include? || (d.groups & groups).empty? }
|
||||
end
|
||||
|
||||
def source_requirements
|
||||
# Load all specs from remote sources
|
||||
index
|
||||
|
||||
# Record the specs available in each gem's source, so that those
|
||||
# specs will be available later when the resolver knows where to
|
||||
# look for that gemspec (or its dependencies)
|
||||
default = sources.default_source
|
||||
source_requirements = { :default => default }
|
||||
default = nil unless Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
|
||||
dependencies.each do |dep|
|
||||
next unless source = dep.source || default
|
||||
source_requirements[dep.name] = source
|
||||
end
|
||||
metadata_dependencies.each do |dep|
|
||||
source_requirements[dep.name] = sources.metadata_source
|
||||
end
|
||||
source_requirements["bundler"] = sources.metadata_source # needs to come last to override
|
||||
source_requirements
|
||||
end
|
||||
|
||||
def pinned_spec_names(skip = nil)
|
||||
pinned_names = []
|
||||
default = Bundler.feature_flag.lockfile_uses_separate_rubygems_sources? && sources.default_source
|
||||
@dependencies.each do |dep|
|
||||
next unless dep_source = dep.source || default
|
||||
next if dep_source == skip
|
||||
pinned_names << dep.name
|
||||
end
|
||||
pinned_names
|
||||
end
|
||||
|
||||
def requested_groups
|
||||
groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with]
|
||||
end
|
||||
|
||||
def lockfiles_equal?(current, proposed, preserve_unknown_sections)
|
||||
if preserve_unknown_sections
|
||||
sections_to_ignore = LockfileParser.sections_to_ignore(@locked_bundler_version)
|
||||
sections_to_ignore += LockfileParser.unknown_sections_in_lockfile(current)
|
||||
sections_to_ignore += LockfileParser::ENVIRONMENT_VERSION_SECTIONS
|
||||
pattern = /#{Regexp.union(sections_to_ignore)}\n(\s{2,}.*\n)+/
|
||||
whitespace_cleanup = /\n{2,}/
|
||||
current = current.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip
|
||||
proposed = proposed.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip
|
||||
end
|
||||
current == proposed
|
||||
end
|
||||
|
||||
def extract_gem_info(error)
|
||||
# This method will extract the error message like "Could not find foo-1.2.3 in any of the sources"
|
||||
# to an array. The first element will be the gem name (e.g. foo), the second will be the version number.
|
||||
error.message.scan(/Could not find (\w+)-(\d+(?:\.\d+)+)/).flatten
|
||||
end
|
||||
|
||||
def compute_requires
|
||||
dependencies.reduce({}) do |requires, dep|
|
||||
next requires unless dep.should_include?
|
||||
requires[dep.name] = Array(dep.autorequire || dep.name).map do |file|
|
||||
# Allow `require: true` as an alias for `require: <name>`
|
||||
file == true ? dep.name : file
|
||||
end
|
||||
requires
|
||||
end
|
||||
end
|
||||
|
||||
def additional_base_requirements_for_resolve
|
||||
return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions?
|
||||
dependencies_by_name = dependencies.inject({}) {|memo, dep| memo.update(dep.name => dep) }
|
||||
@locked_gems.specs.reduce({}) do |requirements, locked_spec|
|
||||
name = locked_spec.name
|
||||
next requirements if @locked_gems.dependencies[name] != dependencies_by_name[name]
|
||||
dep = Gem::Dependency.new(name, ">= #{locked_spec.version}")
|
||||
requirements[name] = DepProxy.new(dep, locked_spec.platform)
|
||||
requirements
|
||||
end.values
|
||||
end
|
||||
|
||||
def equivalent_rubygems_remotes?(source)
|
||||
return false unless source.is_a?(Source::Rubygems)
|
||||
|
||||
Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class DepProxy
|
||||
attr_reader :__platform, :dep
|
||||
|
||||
def initialize(dep, platform)
|
||||
@dep = dep
|
||||
@__platform = platform
|
||||
end
|
||||
|
||||
def hash
|
||||
@hash ||= [dep, __platform].hash
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
return false if other.class != self.class
|
||||
dep == other.dep && __platform == other.__platform
|
||||
end
|
||||
|
||||
alias_method :eql?, :==
|
||||
|
||||
def type
|
||||
@dep.type
|
||||
end
|
||||
|
||||
def name
|
||||
@dep.name
|
||||
end
|
||||
|
||||
def requirement
|
||||
@dep.requirement
|
||||
end
|
||||
|
||||
def to_s
|
||||
s = name.dup
|
||||
s << " (#{requirement})" unless requirement == Gem::Requirement.default
|
||||
s << " #{__platform}" unless __platform == Gem::Platform::RUBY
|
||||
s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def method_missing(*args, &blk)
|
||||
@dep.send(*args, &blk)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,139 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rubygems/dependency"
|
||||
require "bundler/shared_helpers"
|
||||
require "bundler/rubygems_ext"
|
||||
|
||||
module Bundler
|
||||
class Dependency < Gem::Dependency
|
||||
attr_reader :autorequire
|
||||
attr_reader :groups, :platforms, :gemfile
|
||||
|
||||
PLATFORM_MAP = {
|
||||
:ruby => Gem::Platform::RUBY,
|
||||
:ruby_18 => Gem::Platform::RUBY,
|
||||
:ruby_19 => Gem::Platform::RUBY,
|
||||
:ruby_20 => Gem::Platform::RUBY,
|
||||
:ruby_21 => Gem::Platform::RUBY,
|
||||
:ruby_22 => Gem::Platform::RUBY,
|
||||
:ruby_23 => Gem::Platform::RUBY,
|
||||
:ruby_24 => Gem::Platform::RUBY,
|
||||
:ruby_25 => Gem::Platform::RUBY,
|
||||
:mri => Gem::Platform::RUBY,
|
||||
:mri_18 => Gem::Platform::RUBY,
|
||||
:mri_19 => Gem::Platform::RUBY,
|
||||
:mri_20 => Gem::Platform::RUBY,
|
||||
:mri_21 => Gem::Platform::RUBY,
|
||||
:mri_22 => Gem::Platform::RUBY,
|
||||
:mri_23 => Gem::Platform::RUBY,
|
||||
:mri_24 => Gem::Platform::RUBY,
|
||||
:mri_25 => Gem::Platform::RUBY,
|
||||
:rbx => Gem::Platform::RUBY,
|
||||
:truffleruby => Gem::Platform::RUBY,
|
||||
:jruby => Gem::Platform::JAVA,
|
||||
:jruby_18 => Gem::Platform::JAVA,
|
||||
:jruby_19 => Gem::Platform::JAVA,
|
||||
:mswin => Gem::Platform::MSWIN,
|
||||
:mswin_18 => Gem::Platform::MSWIN,
|
||||
:mswin_19 => Gem::Platform::MSWIN,
|
||||
:mswin_20 => Gem::Platform::MSWIN,
|
||||
:mswin_21 => Gem::Platform::MSWIN,
|
||||
:mswin_22 => Gem::Platform::MSWIN,
|
||||
:mswin_23 => Gem::Platform::MSWIN,
|
||||
:mswin_24 => Gem::Platform::MSWIN,
|
||||
:mswin_25 => Gem::Platform::MSWIN,
|
||||
:mswin64 => Gem::Platform::MSWIN64,
|
||||
:mswin64_19 => Gem::Platform::MSWIN64,
|
||||
:mswin64_20 => Gem::Platform::MSWIN64,
|
||||
:mswin64_21 => Gem::Platform::MSWIN64,
|
||||
:mswin64_22 => Gem::Platform::MSWIN64,
|
||||
:mswin64_23 => Gem::Platform::MSWIN64,
|
||||
:mswin64_24 => Gem::Platform::MSWIN64,
|
||||
:mswin64_25 => Gem::Platform::MSWIN64,
|
||||
:mingw => Gem::Platform::MINGW,
|
||||
:mingw_18 => Gem::Platform::MINGW,
|
||||
:mingw_19 => Gem::Platform::MINGW,
|
||||
:mingw_20 => Gem::Platform::MINGW,
|
||||
:mingw_21 => Gem::Platform::MINGW,
|
||||
:mingw_22 => Gem::Platform::MINGW,
|
||||
:mingw_23 => Gem::Platform::MINGW,
|
||||
:mingw_24 => Gem::Platform::MINGW,
|
||||
:mingw_25 => Gem::Platform::MINGW,
|
||||
:x64_mingw => Gem::Platform::X64_MINGW,
|
||||
:x64_mingw_20 => Gem::Platform::X64_MINGW,
|
||||
:x64_mingw_21 => Gem::Platform::X64_MINGW,
|
||||
:x64_mingw_22 => Gem::Platform::X64_MINGW,
|
||||
:x64_mingw_23 => Gem::Platform::X64_MINGW,
|
||||
:x64_mingw_24 => Gem::Platform::X64_MINGW,
|
||||
:x64_mingw_25 => Gem::Platform::X64_MINGW,
|
||||
}.freeze
|
||||
|
||||
REVERSE_PLATFORM_MAP = {}.tap do |reverse_platform_map|
|
||||
PLATFORM_MAP.each do |key, value|
|
||||
reverse_platform_map[value] ||= []
|
||||
reverse_platform_map[value] << key
|
||||
end
|
||||
|
||||
reverse_platform_map.each {|_, platforms| platforms.freeze }
|
||||
end.freeze
|
||||
|
||||
def initialize(name, version, options = {}, &blk)
|
||||
type = options["type"] || :runtime
|
||||
super(name, version, type)
|
||||
|
||||
@autorequire = nil
|
||||
@groups = Array(options["group"] || :default).map(&:to_sym)
|
||||
@source = options["source"]
|
||||
@platforms = Array(options["platforms"])
|
||||
@env = options["env"]
|
||||
@should_include = options.fetch("should_include", true)
|
||||
@gemfile = options["gemfile"]
|
||||
|
||||
@autorequire = Array(options["require"] || []) if options.key?("require")
|
||||
end
|
||||
|
||||
# Returns the platforms this dependency is valid for, in the same order as
|
||||
# passed in the `valid_platforms` parameter
|
||||
def gem_platforms(valid_platforms)
|
||||
return valid_platforms if @platforms.empty?
|
||||
|
||||
@gem_platforms ||= @platforms.map {|pl| PLATFORM_MAP[pl] }.compact.uniq
|
||||
|
||||
valid_platforms & @gem_platforms
|
||||
end
|
||||
|
||||
def should_include?
|
||||
@should_include && current_env? && current_platform?
|
||||
end
|
||||
|
||||
def current_env?
|
||||
return true unless @env
|
||||
if @env.is_a?(Hash)
|
||||
@env.all? do |key, val|
|
||||
ENV[key.to_s] && (val.is_a?(String) ? ENV[key.to_s] == val : ENV[key.to_s] =~ val)
|
||||
end
|
||||
else
|
||||
ENV[@env.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
def current_platform?
|
||||
return true if @platforms.empty?
|
||||
@platforms.any? do |p|
|
||||
Bundler.current_ruby.send("#{p}?")
|
||||
end
|
||||
end
|
||||
|
||||
def to_lock
|
||||
out = super
|
||||
out << "!" if source
|
||||
out << "\n"
|
||||
end
|
||||
|
||||
def specific?
|
||||
super
|
||||
rescue NoMethodError
|
||||
requirement != ">= 0"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/shared_helpers"
|
||||
Bundler::SharedHelpers.major_deprecation 2, "Bundler no longer integrates with " \
|
||||
"Capistrano, but Capistrano provides its own integration with " \
|
||||
"Bundler via the capistrano-bundler gem. Use it instead."
|
||||
|
||||
module Bundler
|
||||
class Deployment
|
||||
def self.define_task(context, task_method = :task, opts = {})
|
||||
if defined?(Capistrano) && context.is_a?(Capistrano::Configuration)
|
||||
context_name = "capistrano"
|
||||
role_default = "{:except => {:no_release => true}}"
|
||||
error_type = ::Capistrano::CommandError
|
||||
else
|
||||
context_name = "vlad"
|
||||
role_default = "[:app]"
|
||||
error_type = ::Rake::CommandFailedError
|
||||
end
|
||||
|
||||
roles = context.fetch(:bundle_roles, false)
|
||||
opts[:roles] = roles if roles
|
||||
|
||||
context.send :namespace, :bundle do
|
||||
send :desc, <<-DESC
|
||||
Install the current Bundler environment. By default, gems will be \
|
||||
installed to the shared/bundle path. Gems in the development and \
|
||||
test group will not be installed. The install command is executed \
|
||||
with the --deployment and --quiet flags. If the bundle cmd cannot \
|
||||
be found then you can override the bundle_cmd variable to specify \
|
||||
which one it should use. The base path to the app is fetched from \
|
||||
the :latest_release variable. Set it for custom deploy layouts.
|
||||
|
||||
You can override any of these defaults by setting the variables shown below.
|
||||
|
||||
N.B. bundle_roles must be defined before you require 'bundler/#{context_name}' \
|
||||
in your deploy.rb file.
|
||||
|
||||
set :bundle_gemfile, "Gemfile"
|
||||
set :bundle_dir, File.join(fetch(:shared_path), 'bundle')
|
||||
set :bundle_flags, "--deployment --quiet"
|
||||
set :bundle_without, [:development, :test]
|
||||
set :bundle_with, [:mysql]
|
||||
set :bundle_cmd, "bundle" # e.g. "/opt/ruby/bin/bundle"
|
||||
set :bundle_roles, #{role_default} # e.g. [:app, :batch]
|
||||
DESC
|
||||
send task_method, :install, opts do
|
||||
bundle_cmd = context.fetch(:bundle_cmd, "bundle")
|
||||
bundle_flags = context.fetch(:bundle_flags, "--deployment --quiet")
|
||||
bundle_dir = context.fetch(:bundle_dir, File.join(context.fetch(:shared_path), "bundle"))
|
||||
bundle_gemfile = context.fetch(:bundle_gemfile, "Gemfile")
|
||||
bundle_without = [*context.fetch(:bundle_without, [:development, :test])].compact
|
||||
bundle_with = [*context.fetch(:bundle_with, [])].compact
|
||||
app_path = context.fetch(:latest_release)
|
||||
if app_path.to_s.empty?
|
||||
raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.")
|
||||
end
|
||||
args = ["--gemfile #{File.join(app_path, bundle_gemfile)}"]
|
||||
args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty?
|
||||
args << bundle_flags.to_s
|
||||
args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty?
|
||||
args << "--with #{bundle_with.join(" ")}" unless bundle_with.empty?
|
||||
|
||||
run "cd #{app_path} && #{bundle_cmd} install #{args.join(" ")}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
begin
|
||||
require "rubygems/deprecate"
|
||||
rescue LoadError
|
||||
# it's fine if it doesn't exist on the current RubyGems...
|
||||
nil
|
||||
end
|
||||
|
||||
module Bundler
|
||||
# If Bundler::Deprecate is an autoload constant, we need to define it
|
||||
if defined?(Bundler::Deprecate) && !autoload?(:Deprecate)
|
||||
# nothing to do!
|
||||
elsif defined? ::Deprecate
|
||||
Deprecate = ::Deprecate
|
||||
elsif defined? Gem::Deprecate
|
||||
Deprecate = Gem::Deprecate
|
||||
else
|
||||
class Deprecate
|
||||
end
|
||||
end
|
||||
|
||||
unless Deprecate.respond_to?(:skip_during)
|
||||
def Deprecate.skip_during
|
||||
original = skip
|
||||
self.skip = true
|
||||
yield
|
||||
ensure
|
||||
self.skip = original
|
||||
end
|
||||
end
|
||||
|
||||
unless Deprecate.respond_to?(:skip)
|
||||
def Deprecate.skip
|
||||
@skip ||= false
|
||||
end
|
||||
end
|
||||
|
||||
unless Deprecate.respond_to?(:skip=)
|
||||
def Deprecate.skip=(skip)
|
||||
@skip = skip
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,615 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/dependency"
|
||||
require "bundler/ruby_dsl"
|
||||
|
||||
module Bundler
|
||||
class Dsl
|
||||
include RubyDsl
|
||||
|
||||
def self.evaluate(gemfile, lockfile, unlock)
|
||||
builder = new
|
||||
builder.eval_gemfile(gemfile)
|
||||
builder.to_definition(lockfile, unlock)
|
||||
end
|
||||
|
||||
VALID_PLATFORMS = Bundler::Dependency::PLATFORM_MAP.keys.freeze
|
||||
|
||||
VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules
|
||||
platform platforms type source install_if gemfile].freeze
|
||||
|
||||
attr_reader :gemspecs
|
||||
attr_accessor :dependencies
|
||||
|
||||
def initialize
|
||||
@source = nil
|
||||
@sources = SourceList.new
|
||||
@git_sources = {}
|
||||
@dependencies = []
|
||||
@groups = []
|
||||
@install_conditionals = []
|
||||
@optional_groups = []
|
||||
@platforms = []
|
||||
@env = nil
|
||||
@ruby_version = nil
|
||||
@gemspecs = []
|
||||
@gemfile = nil
|
||||
@gemfiles = []
|
||||
add_git_sources
|
||||
end
|
||||
|
||||
def eval_gemfile(gemfile, contents = nil)
|
||||
expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile && @gemfile.parent)
|
||||
original_gemfile = @gemfile
|
||||
@gemfile = expanded_gemfile_path
|
||||
@gemfiles << expanded_gemfile_path
|
||||
contents ||= Bundler.read_file(@gemfile.to_s)
|
||||
instance_eval(contents.dup.untaint, gemfile.to_s, 1)
|
||||
rescue Exception => e
|
||||
message = "There was an error " \
|
||||
"#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \
|
||||
"`#{File.basename gemfile.to_s}`: #{e.message}"
|
||||
|
||||
raise DSLError.new(message, gemfile, e.backtrace, contents)
|
||||
ensure
|
||||
@gemfile = original_gemfile
|
||||
end
|
||||
|
||||
def gemspec(opts = nil)
|
||||
opts ||= {}
|
||||
path = opts[:path] || "."
|
||||
glob = opts[:glob]
|
||||
name = opts[:name]
|
||||
development_group = opts[:development_group] || :development
|
||||
expanded_path = gemfile_root.join(path)
|
||||
|
||||
gemspecs = Dir[File.join(expanded_path, "{,*}.gemspec")].map {|g| Bundler.load_gemspec(g) }.compact
|
||||
gemspecs.reject! {|s| s.name != name } if name
|
||||
Index.sort_specs(gemspecs)
|
||||
specs_by_name_and_version = gemspecs.group_by {|s| [s.name, s.version] }
|
||||
|
||||
case specs_by_name_and_version.size
|
||||
when 1
|
||||
specs = specs_by_name_and_version.values.first
|
||||
spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first
|
||||
|
||||
@gemspecs << spec
|
||||
|
||||
gem_platforms = Bundler::Dependency::REVERSE_PLATFORM_MAP[Bundler::GemHelpers.generic_local_platform]
|
||||
gem spec.name, :name => spec.name, :path => path, :glob => glob, :platforms => gem_platforms
|
||||
|
||||
group(development_group) do
|
||||
spec.development_dependencies.each do |dep|
|
||||
gem dep.name, *(dep.requirement.as_list + [:type => :development])
|
||||
end
|
||||
end
|
||||
when 0
|
||||
raise InvalidOption, "There are no gemspecs at #{expanded_path}"
|
||||
else
|
||||
raise InvalidOption, "There are multiple gemspecs at #{expanded_path}. " \
|
||||
"Please use the :name option to specify which one should be used"
|
||||
end
|
||||
end
|
||||
|
||||
def gem(name, *args)
|
||||
options = args.last.is_a?(Hash) ? args.pop.dup : {}
|
||||
options["gemfile"] = @gemfile
|
||||
version = args || [">= 0"]
|
||||
|
||||
normalize_options(name, version, options)
|
||||
|
||||
dep = Dependency.new(name, version, options)
|
||||
|
||||
# if there's already a dependency with this name we try to prefer one
|
||||
if current = @dependencies.find {|d| d.name == dep.name }
|
||||
deleted_dep = @dependencies.delete(current) if current.type == :development
|
||||
|
||||
if current.requirement != dep.requirement
|
||||
unless deleted_dep
|
||||
return if dep.type == :development
|
||||
|
||||
update_prompt = ""
|
||||
|
||||
if File.basename(@gemfile) == Injector::INJECTED_GEMS
|
||||
if dep.requirements_list.include?(">= 0") && !current.requirements_list.include?(">= 0")
|
||||
update_prompt = ". Gem already added"
|
||||
else
|
||||
update_prompt = ". If you want to update the gem version, run `bundle update #{current.name}`"
|
||||
|
||||
update_prompt += ". You may also need to change the version requirement specified in the Gemfile if it's too restrictive." unless current.requirements_list.include?(">= 0")
|
||||
end
|
||||
end
|
||||
|
||||
raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \
|
||||
"You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \
|
||||
"#{update_prompt}"
|
||||
end
|
||||
|
||||
else
|
||||
Bundler.ui.warn "Your Gemfile lists the gem #{current.name} (#{current.requirement}) more than once.\n" \
|
||||
"You should probably keep only one of them.\n" \
|
||||
"Remove any duplicate entries and specify the gem only once (per group).\n" \
|
||||
"While it's not a problem now, it could cause errors if you change the version of one of them later."
|
||||
end
|
||||
|
||||
if current.source != dep.source
|
||||
unless deleted_dep
|
||||
return if dep.type == :development
|
||||
raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \
|
||||
"You specified that #{dep.name} (#{dep.requirement}) should come from " \
|
||||
"#{current.source || "an unspecified source"} and #{dep.source}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@dependencies << dep
|
||||
end
|
||||
|
||||
def source(source, *args, &blk)
|
||||
options = args.last.is_a?(Hash) ? args.pop.dup : {}
|
||||
options = normalize_hash(options)
|
||||
source = normalize_source(source)
|
||||
|
||||
if options.key?("type")
|
||||
options["type"] = options["type"].to_s
|
||||
unless Plugin.source?(options["type"])
|
||||
raise InvalidOption, "No plugin sources available for #{options["type"]}"
|
||||
end
|
||||
|
||||
unless block_given?
|
||||
raise InvalidOption, "You need to pass a block to #source with :type option"
|
||||
end
|
||||
|
||||
source_opts = options.merge("uri" => source)
|
||||
with_source(@sources.add_plugin_source(options["type"], source_opts), &blk)
|
||||
elsif block_given?
|
||||
with_source(@sources.add_rubygems_source("remotes" => source), &blk)
|
||||
else
|
||||
check_primary_source_safety(@sources)
|
||||
@sources.global_rubygems_source = source
|
||||
end
|
||||
end
|
||||
|
||||
def git_source(name, &block)
|
||||
unless block_given?
|
||||
raise InvalidOption, "You need to pass a block to #git_source"
|
||||
end
|
||||
|
||||
if valid_keys.include?(name.to_s)
|
||||
raise InvalidOption, "You cannot use #{name} as a git source. It " \
|
||||
"is a reserved key. Reserved keys are: #{valid_keys.join(", ")}"
|
||||
end
|
||||
|
||||
@git_sources[name.to_s] = block
|
||||
end
|
||||
|
||||
def path(path, options = {}, &blk)
|
||||
unless block_given?
|
||||
msg = "You can no longer specify a path source by itself. Instead, \n" \
|
||||
"either use the :path option on a gem, or specify the gems that \n" \
|
||||
"bundler should find in the path source by passing a block to \n" \
|
||||
"the path method, like: \n\n" \
|
||||
" path 'dir/containing/rails' do\n" \
|
||||
" gem 'rails'\n" \
|
||||
" end\n\n"
|
||||
|
||||
raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource?
|
||||
SharedHelpers.major_deprecation(2, msg.strip)
|
||||
end
|
||||
|
||||
source_options = normalize_hash(options).merge(
|
||||
"path" => Pathname.new(path),
|
||||
"root_path" => gemfile_root,
|
||||
"gemspec" => gemspecs.find {|g| g.name == options["name"] }
|
||||
)
|
||||
source = @sources.add_path_source(source_options)
|
||||
with_source(source, &blk)
|
||||
end
|
||||
|
||||
def git(uri, options = {}, &blk)
|
||||
unless block_given?
|
||||
msg = "You can no longer specify a git source by itself. Instead, \n" \
|
||||
"either use the :git option on a gem, or specify the gems that \n" \
|
||||
"bundler should find in the git source by passing a block to \n" \
|
||||
"the git method, like: \n\n" \
|
||||
" git 'git://github.com/rails/rails.git' do\n" \
|
||||
" gem 'rails'\n" \
|
||||
" end"
|
||||
raise DeprecatedError, msg
|
||||
end
|
||||
|
||||
with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk)
|
||||
end
|
||||
|
||||
def github(repo, options = {})
|
||||
raise ArgumentError, "GitHub sources require a block" unless block_given?
|
||||
raise DeprecatedError, "The #github method has been removed" if Bundler.feature_flag.skip_default_git_sources?
|
||||
github_uri = @git_sources["github"].call(repo)
|
||||
git_options = normalize_hash(options).merge("uri" => github_uri)
|
||||
git_source = @sources.add_git_source(git_options)
|
||||
with_source(git_source) { yield }
|
||||
end
|
||||
|
||||
def to_definition(lockfile, unlock)
|
||||
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
|
||||
end
|
||||
|
||||
def group(*args, &blk)
|
||||
options = args.last.is_a?(Hash) ? args.pop.dup : {}
|
||||
normalize_group_options(options, args)
|
||||
|
||||
@groups.concat args
|
||||
|
||||
if options["optional"]
|
||||
optional_groups = args - @optional_groups
|
||||
@optional_groups.concat optional_groups
|
||||
end
|
||||
|
||||
yield
|
||||
ensure
|
||||
args.each { @groups.pop }
|
||||
end
|
||||
|
||||
def install_if(*args)
|
||||
@install_conditionals.concat args
|
||||
yield
|
||||
ensure
|
||||
args.each { @install_conditionals.pop }
|
||||
end
|
||||
|
||||
def platforms(*platforms)
|
||||
@platforms.concat platforms
|
||||
yield
|
||||
ensure
|
||||
platforms.each { @platforms.pop }
|
||||
end
|
||||
alias_method :platform, :platforms
|
||||
|
||||
def env(name)
|
||||
old = @env
|
||||
@env = name
|
||||
yield
|
||||
ensure
|
||||
@env = old
|
||||
end
|
||||
|
||||
def plugin(*args)
|
||||
# Pass on
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_git_sources
|
||||
return if Bundler.feature_flag.skip_default_git_sources?
|
||||
|
||||
git_source(:github) do |repo_name|
|
||||
warn_deprecated_git_source(:github, <<-'RUBY'.strip, 'Change any "reponame" :github sources to "username/reponame".')
|
||||
"https://github.com/#{repo_name}.git"
|
||||
RUBY
|
||||
# It would be better to use https instead of the git protocol, but this
|
||||
# can break deployment of existing locked bundles when switching between
|
||||
# different versions of Bundler. The change will be made in 2.0, which
|
||||
# does not guarantee compatibility with the 1.x series.
|
||||
#
|
||||
# See https://github.com/bundler/bundler/pull/2569 for discussion
|
||||
#
|
||||
# This can be overridden by adding this code to your Gemfiles:
|
||||
#
|
||||
# git_source(:github) do |repo_name|
|
||||
# repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
||||
# "https://github.com/#{repo_name}.git"
|
||||
# end
|
||||
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
||||
# TODO: 2.0 upgrade this setting to the default
|
||||
if Bundler.settings["github.https"]
|
||||
Bundler::SharedHelpers.major_deprecation 2, "The `github.https` setting will be removed"
|
||||
"https://github.com/#{repo_name}.git"
|
||||
else
|
||||
"git://github.com/#{repo_name}.git"
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: 2.0 remove this deprecated git source
|
||||
git_source(:gist) do |repo_name|
|
||||
warn_deprecated_git_source(:gist, '"https://gist.github.com/#{repo_name}.git"')
|
||||
|
||||
"https://gist.github.com/#{repo_name}.git"
|
||||
end
|
||||
|
||||
# TODO: 2.0 remove this deprecated git source
|
||||
git_source(:bitbucket) do |repo_name|
|
||||
warn_deprecated_git_source(:bitbucket, <<-'RUBY'.strip)
|
||||
user_name, repo_name = repo_name.split("/")
|
||||
repo_name ||= user_name
|
||||
"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
|
||||
RUBY
|
||||
|
||||
user_name, repo_name = repo_name.split("/")
|
||||
repo_name ||= user_name
|
||||
"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
|
||||
end
|
||||
end
|
||||
|
||||
def with_source(source)
|
||||
old_source = @source
|
||||
if block_given?
|
||||
@source = source
|
||||
yield
|
||||
end
|
||||
source
|
||||
ensure
|
||||
@source = old_source
|
||||
end
|
||||
|
||||
def normalize_hash(opts)
|
||||
opts.keys.each do |k|
|
||||
opts[k.to_s] = opts.delete(k) unless k.is_a?(String)
|
||||
end
|
||||
opts
|
||||
end
|
||||
|
||||
def valid_keys
|
||||
@valid_keys ||= VALID_KEYS
|
||||
end
|
||||
|
||||
def normalize_options(name, version, opts)
|
||||
if name.is_a?(Symbol)
|
||||
raise GemfileError, %(You need to specify gem names as Strings. Use 'gem "#{name}"' instead)
|
||||
end
|
||||
if name =~ /\s/
|
||||
raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace)
|
||||
end
|
||||
if name.empty?
|
||||
raise GemfileError, %(an empty gem name is not valid)
|
||||
end
|
||||
|
||||
normalize_hash(opts)
|
||||
|
||||
git_names = @git_sources.keys.map(&:to_s)
|
||||
validate_keys("gem '#{name}'", opts, valid_keys + git_names)
|
||||
|
||||
groups = @groups.dup
|
||||
opts["group"] = opts.delete("groups") || opts["group"]
|
||||
groups.concat Array(opts.delete("group"))
|
||||
groups = [:default] if groups.empty?
|
||||
|
||||
install_if = @install_conditionals.dup
|
||||
install_if.concat Array(opts.delete("install_if"))
|
||||
install_if = install_if.reduce(true) do |memo, val|
|
||||
memo && (val.respond_to?(:call) ? val.call : val)
|
||||
end
|
||||
|
||||
platforms = @platforms.dup
|
||||
opts["platforms"] = opts["platform"] || opts["platforms"]
|
||||
platforms.concat Array(opts.delete("platforms"))
|
||||
platforms.map!(&:to_sym)
|
||||
platforms.each do |p|
|
||||
next if VALID_PLATFORMS.include?(p)
|
||||
raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
|
||||
end
|
||||
|
||||
# Save sources passed in a key
|
||||
if opts.key?("source")
|
||||
source = normalize_source(opts["source"])
|
||||
opts["source"] = @sources.add_rubygems_source("remotes" => source)
|
||||
end
|
||||
|
||||
git_name = (git_names & opts.keys).last
|
||||
if @git_sources[git_name]
|
||||
opts["git"] = @git_sources[git_name].call(opts[git_name])
|
||||
end
|
||||
|
||||
%w[git path].each do |type|
|
||||
next unless param = opts[type]
|
||||
if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/
|
||||
options = opts.merge("name" => name, "version" => $1)
|
||||
else
|
||||
options = opts.dup
|
||||
end
|
||||
source = send(type, param, options) {}
|
||||
opts["source"] = source
|
||||
end
|
||||
|
||||
opts["source"] ||= @source
|
||||
opts["env"] ||= @env
|
||||
opts["platforms"] = platforms.dup
|
||||
opts["group"] = groups
|
||||
opts["should_include"] = install_if
|
||||
end
|
||||
|
||||
def normalize_group_options(opts, groups)
|
||||
normalize_hash(opts)
|
||||
|
||||
groups = groups.map {|group| ":#{group}" }.join(", ")
|
||||
validate_keys("group #{groups}", opts, %w[optional])
|
||||
|
||||
opts["optional"] ||= false
|
||||
end
|
||||
|
||||
def validate_keys(command, opts, valid_keys)
|
||||
invalid_keys = opts.keys - valid_keys
|
||||
|
||||
git_source = opts.keys & @git_sources.keys.map(&:to_s)
|
||||
if opts["branch"] && !(opts["git"] || opts["github"] || git_source.any?)
|
||||
raise GemfileError, %(The `branch` option for `#{command}` is not allowed. Only gems with a git source can specify a branch)
|
||||
end
|
||||
|
||||
return true unless invalid_keys.any?
|
||||
|
||||
message = String.new
|
||||
message << "You passed #{invalid_keys.map {|k| ":" + k }.join(", ")} "
|
||||
message << if invalid_keys.size > 1
|
||||
"as options for #{command}, but they are invalid."
|
||||
else
|
||||
"as an option for #{command}, but it is invalid."
|
||||
end
|
||||
|
||||
message << " Valid options are: #{valid_keys.join(", ")}."
|
||||
message << " You may be able to resolve this by upgrading Bundler to the newest version."
|
||||
raise InvalidOption, message
|
||||
end
|
||||
|
||||
def normalize_source(source)
|
||||
case source
|
||||
when :gemcutter, :rubygems, :rubyforge
|
||||
Bundler::SharedHelpers.major_deprecation 2, "The source :#{source} is deprecated because HTTP " \
|
||||
"requests are insecure.\nPlease change your source to 'https://" \
|
||||
"rubygems.org' if possible, or 'http://rubygems.org' if not."
|
||||
"http://rubygems.org"
|
||||
when String
|
||||
source
|
||||
else
|
||||
raise GemfileError, "Unknown source '#{source}'"
|
||||
end
|
||||
end
|
||||
|
||||
def check_primary_source_safety(source_list)
|
||||
return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?
|
||||
|
||||
if Bundler.feature_flag.disable_multisource?
|
||||
msg = "This Gemfile contains multiple primary sources. " \
|
||||
"Each source after the first must include a block to indicate which gems " \
|
||||
"should come from that source"
|
||||
unless Bundler.feature_flag.bundler_2_mode?
|
||||
msg += ". To downgrade this error to a warning, run " \
|
||||
"`bundle config --delete disable_multisource`"
|
||||
end
|
||||
raise GemfileEvalError, msg
|
||||
else
|
||||
Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
|
||||
"Using `source` more than once without a block is a security risk, and " \
|
||||
"may result in installing unexpected gems. To resolve this warning, use " \
|
||||
"a block to indicate which gems should come from the secondary source. " \
|
||||
"To upgrade this warning to an error, run `bundle config " \
|
||||
"disable_multisource true`."
|
||||
end
|
||||
end
|
||||
|
||||
def warn_deprecated_git_source(name, replacement, additional_message = nil)
|
||||
# TODO: 2.0 remove deprecation
|
||||
additional_message &&= " #{additional_message}"
|
||||
replacement = if replacement.count("\n").zero?
|
||||
"{|repo_name| #{replacement} }"
|
||||
else
|
||||
"do |repo_name|\n#{replacement.to_s.gsub(/^/, " ")}\n end"
|
||||
end
|
||||
|
||||
Bundler::SharedHelpers.major_deprecation 2, <<-EOS
|
||||
The :#{name} git source is deprecated, and will be removed in Bundler 2.0.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work:
|
||||
|
||||
git_source(:#{name}) #{replacement}
|
||||
|
||||
EOS
|
||||
end
|
||||
|
||||
class DSLError < GemfileError
|
||||
# @return [String] the description that should be presented to the user.
|
||||
#
|
||||
attr_reader :description
|
||||
|
||||
# @return [String] the path of the dsl file that raised the exception.
|
||||
#
|
||||
attr_reader :dsl_path
|
||||
|
||||
# @return [Exception] the backtrace of the exception raised by the
|
||||
# evaluation of the dsl file.
|
||||
#
|
||||
attr_reader :backtrace
|
||||
|
||||
# @param [Exception] backtrace @see backtrace
|
||||
# @param [String] dsl_path @see dsl_path
|
||||
#
|
||||
def initialize(description, dsl_path, backtrace, contents = nil)
|
||||
@status_code = $!.respond_to?(:status_code) && $!.status_code
|
||||
|
||||
@description = description
|
||||
@dsl_path = dsl_path
|
||||
@backtrace = backtrace
|
||||
@contents = contents
|
||||
end
|
||||
|
||||
def status_code
|
||||
@status_code || super
|
||||
end
|
||||
|
||||
# @return [String] the contents of the DSL that cause the exception to
|
||||
# be raised.
|
||||
#
|
||||
def contents
|
||||
@contents ||= begin
|
||||
dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
|
||||
end
|
||||
end
|
||||
|
||||
# The message of the exception reports the content of podspec for the
|
||||
# line that generated the original exception.
|
||||
#
|
||||
# @example Output
|
||||
#
|
||||
# Invalid podspec at `RestKit.podspec` - undefined method
|
||||
# `exclude_header_search_paths=' for #<Pod::Specification for
|
||||
# `RestKit/Network (0.9.3)`>
|
||||
#
|
||||
# from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36
|
||||
# -------------------------------------------
|
||||
# # because it would break: #import <CoreData/CoreData.h>
|
||||
# > ns.exclude_header_search_paths = 'Code/RestKit.h'
|
||||
# end
|
||||
# -------------------------------------------
|
||||
#
|
||||
# @return [String] the message of the exception.
|
||||
#
|
||||
def to_s
|
||||
@to_s ||= begin
|
||||
trace_line, description = parse_line_number_from_description
|
||||
|
||||
m = String.new("\n[!] ")
|
||||
m << description
|
||||
m << ". Bundler cannot continue.\n"
|
||||
|
||||
return m unless backtrace && dsl_path && contents
|
||||
|
||||
trace_line = backtrace.find {|l| l.include?(dsl_path.to_s) } || trace_line
|
||||
return m unless trace_line
|
||||
line_numer = trace_line.split(":")[1].to_i - 1
|
||||
return m unless line_numer
|
||||
|
||||
lines = contents.lines.to_a
|
||||
indent = " # "
|
||||
indicator = indent.tr("#", ">")
|
||||
first_line = line_numer.zero?
|
||||
last_line = (line_numer == (lines.count - 1))
|
||||
|
||||
m << "\n"
|
||||
m << "#{indent}from #{trace_line.gsub(/:in.*$/, "")}\n"
|
||||
m << "#{indent}-------------------------------------------\n"
|
||||
m << "#{indent}#{lines[line_numer - 1]}" unless first_line
|
||||
m << "#{indicator}#{lines[line_numer]}"
|
||||
m << "#{indent}#{lines[line_numer + 1]}" unless last_line
|
||||
m << "\n" unless m.end_with?("\n")
|
||||
m << "#{indent}-------------------------------------------\n"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_line_number_from_description
|
||||
description = self.description
|
||||
if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/
|
||||
trace_line = Regexp.last_match[1]
|
||||
description = description.sub(/#{Regexp.quote trace_line}:\s*/, "").sub("\n", " - ")
|
||||
end
|
||||
[trace_line, description]
|
||||
end
|
||||
end
|
||||
|
||||
def gemfile_root
|
||||
@gemfile ||= Bundler.default_gemfile
|
||||
@gemfile.dirname
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,141 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# used for Creating Specifications from the Gemcutter Endpoint
|
||||
class EndpointSpecification < Gem::Specification
|
||||
ILLFORMED_MESSAGE = 'Ill-formed requirement ["#<YAML::Syck::DefaultKey'.freeze
|
||||
include MatchPlatform
|
||||
|
||||
attr_reader :name, :version, :platform, :required_rubygems_version, :required_ruby_version, :checksum
|
||||
attr_accessor :source, :remote, :dependencies
|
||||
|
||||
def initialize(name, version, platform, dependencies, metadata = nil)
|
||||
super()
|
||||
@name = name
|
||||
@version = Gem::Version.create version
|
||||
@platform = platform
|
||||
@dependencies = dependencies.map {|dep, reqs| build_dependency(dep, reqs) }
|
||||
|
||||
@loaded_from = nil
|
||||
@remote_specification = nil
|
||||
|
||||
parse_metadata(metadata)
|
||||
end
|
||||
|
||||
def fetch_platform
|
||||
@platform
|
||||
end
|
||||
|
||||
# needed for standalone, load required_paths from local gemspec
|
||||
# after the gem is installed
|
||||
def require_paths
|
||||
if @remote_specification
|
||||
@remote_specification.require_paths
|
||||
elsif _local_specification
|
||||
_local_specification.require_paths
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# needed for inline
|
||||
def load_paths
|
||||
# remote specs aren't installed, and can't have load_paths
|
||||
if _local_specification
|
||||
_local_specification.load_paths
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# needed for binstubs
|
||||
def executables
|
||||
if @remote_specification
|
||||
@remote_specification.executables
|
||||
elsif _local_specification
|
||||
_local_specification.executables
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# needed for bundle clean
|
||||
def bindir
|
||||
if @remote_specification
|
||||
@remote_specification.bindir
|
||||
elsif _local_specification
|
||||
_local_specification.bindir
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# needed for post_install_messages during install
|
||||
def post_install_message
|
||||
if @remote_specification
|
||||
@remote_specification.post_install_message
|
||||
elsif _local_specification
|
||||
_local_specification.post_install_message
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# needed for "with native extensions" during install
|
||||
def extensions
|
||||
if @remote_specification
|
||||
@remote_specification.extensions
|
||||
elsif _local_specification
|
||||
_local_specification.extensions
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def _local_specification
|
||||
return unless @loaded_from && File.exist?(local_specification_path)
|
||||
eval(File.read(local_specification_path)).tap do |spec|
|
||||
spec.loaded_from = @loaded_from
|
||||
end
|
||||
end
|
||||
|
||||
def __swap__(spec)
|
||||
SharedHelpers.ensure_same_dependencies(self, dependencies, spec.dependencies)
|
||||
@remote_specification = spec
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def local_specification_path
|
||||
"#{base_dir}/specifications/#{full_name}.gemspec"
|
||||
end
|
||||
|
||||
def parse_metadata(data)
|
||||
return unless data
|
||||
data.each do |k, v|
|
||||
next unless v
|
||||
case k.to_s
|
||||
when "checksum"
|
||||
@checksum = v.last
|
||||
when "rubygems"
|
||||
@required_rubygems_version = Gem::Requirement.new(v)
|
||||
when "ruby"
|
||||
@required_ruby_version = Gem::Requirement.new(v)
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
raise GemspecError, "There was an error parsing the metadata for the gem #{name} (#{version}): #{e.class}\n#{e}\nThe metadata was #{data.inspect}"
|
||||
end
|
||||
|
||||
def build_dependency(name, requirements)
|
||||
Gem::Dependency.new(name, requirements)
|
||||
rescue ArgumentError => e
|
||||
raise unless e.message.include?(ILLFORMED_MESSAGE)
|
||||
puts # we shouldn't print the error message on the "fetching info" status line
|
||||
raise GemspecError,
|
||||
"Unfortunately, the gem #{name} (#{version}) has an invalid " \
|
||||
"gemspec.\nPlease ask the gem author to yank the bad version to fix " \
|
||||
"this issue. For more information, see http://bit.ly/syck-defaultkey."
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,155 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/rubygems_integration"
|
||||
require "bundler/source/git/git_proxy"
|
||||
|
||||
module Bundler
|
||||
class Env
|
||||
def self.write(io)
|
||||
io.write report
|
||||
end
|
||||
|
||||
def self.report(options = {})
|
||||
print_gemfile = options.delete(:print_gemfile) { true }
|
||||
print_gemspecs = options.delete(:print_gemspecs) { true }
|
||||
|
||||
out = String.new
|
||||
append_formatted_table("Environment", environment, out)
|
||||
append_formatted_table("Bundler Build Metadata", BuildMetadata.to_h, out)
|
||||
|
||||
unless Bundler.settings.all.empty?
|
||||
out << "\n## Bundler settings\n\n```\n"
|
||||
Bundler.settings.all.each do |setting|
|
||||
out << setting << "\n"
|
||||
Bundler.settings.pretty_values_for(setting).each do |line|
|
||||
out << " " << line << "\n"
|
||||
end
|
||||
end
|
||||
out << "```\n"
|
||||
end
|
||||
|
||||
return out unless SharedHelpers.in_bundle?
|
||||
|
||||
if print_gemfile
|
||||
gemfiles = [Bundler.default_gemfile]
|
||||
begin
|
||||
gemfiles = Bundler.definition.gemfiles
|
||||
rescue GemfileNotFound
|
||||
nil
|
||||
end
|
||||
|
||||
out << "\n## Gemfile\n"
|
||||
gemfiles.each do |gemfile|
|
||||
out << "\n### #{Pathname.new(gemfile).relative_path_from(SharedHelpers.pwd)}\n\n"
|
||||
out << "```ruby\n" << read_file(gemfile).chomp << "\n```\n"
|
||||
end
|
||||
|
||||
out << "\n### #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}\n\n"
|
||||
out << "```\n" << read_file(Bundler.default_lockfile).chomp << "\n```\n"
|
||||
end
|
||||
|
||||
if print_gemspecs
|
||||
dsl = Dsl.new.tap {|d| d.eval_gemfile(Bundler.default_gemfile) }
|
||||
out << "\n## Gemspecs\n" unless dsl.gemspecs.empty?
|
||||
dsl.gemspecs.each do |gs|
|
||||
out << "\n### #{File.basename(gs.loaded_from)}"
|
||||
out << "\n\n```ruby\n" << read_file(gs.loaded_from).chomp << "\n```\n"
|
||||
end
|
||||
end
|
||||
|
||||
out
|
||||
end
|
||||
|
||||
def self.read_file(filename)
|
||||
Bundler.read_file(filename.to_s).strip
|
||||
rescue Errno::ENOENT
|
||||
"<No #{filename} found>"
|
||||
rescue RuntimeError => e
|
||||
"#{e.class}: #{e.message}"
|
||||
end
|
||||
|
||||
def self.ruby_version
|
||||
str = String.new("#{RUBY_VERSION}")
|
||||
if RUBY_VERSION < "1.9"
|
||||
str << " (#{RUBY_RELEASE_DATE}"
|
||||
str << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
|
||||
str << ") [#{RUBY_PLATFORM}]"
|
||||
else
|
||||
str << "p#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
|
||||
str << " (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]"
|
||||
end
|
||||
end
|
||||
|
||||
def self.git_version
|
||||
Bundler::Source::Git::GitProxy.new(nil, nil, nil).full_version
|
||||
rescue Bundler::Source::Git::GitNotInstalledError
|
||||
"not installed"
|
||||
end
|
||||
|
||||
def self.version_of(script)
|
||||
return "not installed" unless Bundler.which(script)
|
||||
`#{script} --version`.chomp
|
||||
end
|
||||
|
||||
def self.chruby_version
|
||||
return "not installed" unless Bundler.which("chruby-exec")
|
||||
`chruby-exec -- chruby --version`.
|
||||
sub(/.*^chruby: (#{Gem::Version::VERSION_PATTERN}).*/m, '\1')
|
||||
end
|
||||
|
||||
def self.environment
|
||||
out = []
|
||||
|
||||
out << ["Bundler", Bundler::VERSION]
|
||||
out << [" Platforms", Gem.platforms.join(", ")]
|
||||
out << ["Ruby", ruby_version]
|
||||
out << [" Full Path", Gem.ruby]
|
||||
out << [" Config Dir", Pathname.new(Gem::ConfigFile::SYSTEM_WIDE_CONFIG_FILE).dirname]
|
||||
out << ["RubyGems", Gem::VERSION]
|
||||
out << [" Gem Home", ENV.fetch("GEM_HOME") { Gem.dir }]
|
||||
out << [" Gem Path", ENV.fetch("GEM_PATH") { Gem.path.join(File::PATH_SEPARATOR) }]
|
||||
out << [" User Path", Gem.user_dir]
|
||||
out << [" Bin Dir", Gem.bindir]
|
||||
if defined?(OpenSSL)
|
||||
out << ["OpenSSL"]
|
||||
out << [" Compiled", OpenSSL::OPENSSL_VERSION] if defined?(OpenSSL::OPENSSL_VERSION)
|
||||
out << [" Loaded", OpenSSL::OPENSSL_LIBRARY_VERSION] if defined?(OpenSSL::OPENSSL_LIBRARY_VERSION)
|
||||
out << [" Cert File", OpenSSL::X509::DEFAULT_CERT_FILE] if defined?(OpenSSL::X509::DEFAULT_CERT_FILE)
|
||||
out << [" Cert Dir", OpenSSL::X509::DEFAULT_CERT_DIR] if defined?(OpenSSL::X509::DEFAULT_CERT_DIR)
|
||||
end
|
||||
out << ["Tools"]
|
||||
out << [" Git", git_version]
|
||||
out << [" RVM", ENV.fetch("rvm_version") { version_of("rvm") }]
|
||||
out << [" rbenv", version_of("rbenv")]
|
||||
out << [" chruby", chruby_version]
|
||||
|
||||
%w[rubygems-bundler open_gem].each do |name|
|
||||
specs = Bundler.rubygems.find_name(name)
|
||||
out << [" #{name}", "(#{specs.map(&:version).join(",")})"] unless specs.empty?
|
||||
end
|
||||
if (exe = caller.last.split(":").first) && exe =~ %r{(exe|bin)/bundler?\z}
|
||||
shebang = File.read(exe).lines.first
|
||||
shebang.sub!(/^#!\s*/, "")
|
||||
unless shebang.start_with?(Gem.ruby, "/usr/bin/env ruby")
|
||||
out << ["Gem.ruby", Gem.ruby]
|
||||
out << ["bundle #!", shebang]
|
||||
end
|
||||
end
|
||||
|
||||
out
|
||||
end
|
||||
|
||||
def self.append_formatted_table(title, pairs, out)
|
||||
return if pairs.empty?
|
||||
out << "\n" unless out.empty?
|
||||
out << "## #{title}\n\n```\n"
|
||||
ljust = pairs.map {|k, _v| k.to_s.length }.max
|
||||
pairs.each do |k, v|
|
||||
out << "#{k.to_s.ljust(ljust)} #{v}\n"
|
||||
end
|
||||
out << "```\n"
|
||||
end
|
||||
|
||||
private_class_method :read_file, :ruby_version, :git_version, :append_formatted_table, :version_of, :chruby_version
|
||||
end
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class EnvironmentPreserver
|
||||
INTENTIONALLY_NIL = "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL".freeze
|
||||
BUNDLER_KEYS = %w[
|
||||
BUNDLE_BIN_PATH
|
||||
BUNDLE_GEMFILE
|
||||
BUNDLER_ORIG_MANPATH
|
||||
BUNDLER_VERSION
|
||||
GEM_HOME
|
||||
GEM_PATH
|
||||
MANPATH
|
||||
PATH
|
||||
RB_USER_INSTALL
|
||||
RUBYLIB
|
||||
RUBYOPT
|
||||
].map(&:freeze).freeze
|
||||
BUNDLER_PREFIX = "BUNDLER_ORIG_".freeze
|
||||
|
||||
# @param env [ENV]
|
||||
# @param keys [Array<String>]
|
||||
def initialize(env, keys)
|
||||
@original = env.to_hash
|
||||
@keys = keys
|
||||
@prefix = BUNDLER_PREFIX
|
||||
end
|
||||
|
||||
# @return [Hash]
|
||||
def backup
|
||||
env = @original.clone
|
||||
@keys.each do |key|
|
||||
value = env[key]
|
||||
if !value.nil? && !value.empty?
|
||||
env[@prefix + key] ||= value
|
||||
elsif value.nil?
|
||||
env[@prefix + key] ||= INTENTIONALLY_NIL
|
||||
end
|
||||
end
|
||||
env
|
||||
end
|
||||
|
||||
# @return [Hash]
|
||||
def restore
|
||||
env = @original.clone
|
||||
@keys.each do |key|
|
||||
value_original = env[@prefix + key]
|
||||
next if value_original.nil? || value_original.empty?
|
||||
if value_original == INTENTIONALLY_NIL
|
||||
env.delete(key)
|
||||
else
|
||||
env[key] = value_original
|
||||
end
|
||||
env.delete(@prefix + key)
|
||||
end
|
||||
env
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,158 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class BundlerError < StandardError
|
||||
def self.status_code(code)
|
||||
define_method(:status_code) { code }
|
||||
if match = BundlerError.all_errors.find {|_k, v| v == code }
|
||||
error, _ = match
|
||||
raise ArgumentError,
|
||||
"Trying to register #{self} for status code #{code} but #{error} is already registered"
|
||||
end
|
||||
BundlerError.all_errors[self] = code
|
||||
end
|
||||
|
||||
def self.all_errors
|
||||
@all_errors ||= {}
|
||||
end
|
||||
end
|
||||
|
||||
class GemfileError < BundlerError; status_code(4); end
|
||||
class InstallError < BundlerError; status_code(5); end
|
||||
|
||||
# Internal error, should be rescued
|
||||
class VersionConflict < BundlerError
|
||||
attr_reader :conflicts
|
||||
|
||||
def initialize(conflicts, msg = nil)
|
||||
super(msg)
|
||||
@conflicts = conflicts
|
||||
end
|
||||
|
||||
status_code(6)
|
||||
end
|
||||
|
||||
class GemNotFound < BundlerError; status_code(7); end
|
||||
class InstallHookError < BundlerError; status_code(8); end
|
||||
class GemfileNotFound < BundlerError; status_code(10); end
|
||||
class GitError < BundlerError; status_code(11); end
|
||||
class DeprecatedError < BundlerError; status_code(12); end
|
||||
class PathError < BundlerError; status_code(13); end
|
||||
class GemspecError < BundlerError; status_code(14); end
|
||||
class InvalidOption < BundlerError; status_code(15); end
|
||||
class ProductionError < BundlerError; status_code(16); end
|
||||
class HTTPError < BundlerError
|
||||
status_code(17)
|
||||
def filter_uri(uri)
|
||||
URICredentialsFilter.credential_filtered_uri(uri)
|
||||
end
|
||||
end
|
||||
class RubyVersionMismatch < BundlerError; status_code(18); end
|
||||
class SecurityError < BundlerError; status_code(19); end
|
||||
class LockfileError < BundlerError; status_code(20); end
|
||||
class CyclicDependencyError < BundlerError; status_code(21); end
|
||||
class GemfileLockNotFound < BundlerError; status_code(22); end
|
||||
class PluginError < BundlerError; status_code(29); end
|
||||
class SudoNotPermittedError < BundlerError; status_code(30); end
|
||||
class ThreadCreationError < BundlerError; status_code(33); end
|
||||
class APIResponseMismatchError < BundlerError; status_code(34); end
|
||||
class GemfileEvalError < GemfileError; end
|
||||
class MarshalError < StandardError; end
|
||||
|
||||
class PermissionError < BundlerError
|
||||
def initialize(path, permission_type = :write)
|
||||
@path = path
|
||||
@permission_type = permission_type
|
||||
end
|
||||
|
||||
def action
|
||||
case @permission_type
|
||||
when :read then "read from"
|
||||
when :write then "write to"
|
||||
when :executable, :exec then "execute"
|
||||
else @permission_type.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def message
|
||||
"There was an error while trying to #{action} `#{@path}`. " \
|
||||
"It is likely that you need to grant #{@permission_type} permissions " \
|
||||
"for that path."
|
||||
end
|
||||
|
||||
status_code(23)
|
||||
end
|
||||
|
||||
class GemRequireError < BundlerError
|
||||
attr_reader :orig_exception
|
||||
|
||||
def initialize(orig_exception, msg)
|
||||
full_message = msg + "\nGem Load Error is: #{orig_exception.message}\n"\
|
||||
"Backtrace for gem load error is:\n"\
|
||||
"#{orig_exception.backtrace.join("\n")}\n"\
|
||||
"Bundler Error Backtrace:\n"
|
||||
super(full_message)
|
||||
@orig_exception = orig_exception
|
||||
end
|
||||
|
||||
status_code(24)
|
||||
end
|
||||
|
||||
class YamlSyntaxError < BundlerError
|
||||
attr_reader :orig_exception
|
||||
|
||||
def initialize(orig_exception, msg)
|
||||
super(msg)
|
||||
@orig_exception = orig_exception
|
||||
end
|
||||
|
||||
status_code(25)
|
||||
end
|
||||
|
||||
class TemporaryResourceError < PermissionError
|
||||
def message
|
||||
"There was an error while trying to #{action} `#{@path}`. " \
|
||||
"Some resource was temporarily unavailable. It's suggested that you try" \
|
||||
"the operation again."
|
||||
end
|
||||
|
||||
status_code(26)
|
||||
end
|
||||
|
||||
class VirtualProtocolError < BundlerError
|
||||
def message
|
||||
"There was an error relating to virtualization and file access." \
|
||||
"It is likely that you need to grant access to or mount some file system correctly."
|
||||
end
|
||||
|
||||
status_code(27)
|
||||
end
|
||||
|
||||
class OperationNotSupportedError < PermissionError
|
||||
def message
|
||||
"Attempting to #{action} `#{@path}` is unsupported by your OS."
|
||||
end
|
||||
|
||||
status_code(28)
|
||||
end
|
||||
|
||||
class NoSpaceOnDeviceError < PermissionError
|
||||
def message
|
||||
"There was an error while trying to #{action} `#{@path}`. " \
|
||||
"There was insufficient space remaining on the device."
|
||||
end
|
||||
|
||||
status_code(31)
|
||||
end
|
||||
|
||||
class GenericSystemCallError < BundlerError
|
||||
attr_reader :underlying_error
|
||||
|
||||
def initialize(underlying_error, message)
|
||||
@underlying_error = underlying_error
|
||||
super("#{message}\nThe underlying system error is #{@underlying_error.class}: #{@underlying_error}")
|
||||
end
|
||||
|
||||
status_code(32)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class FeatureFlag
|
||||
def self.settings_flag(flag, &default)
|
||||
unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s)
|
||||
raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key"
|
||||
end
|
||||
|
||||
settings_method("#{flag}?", flag, &default)
|
||||
end
|
||||
private_class_method :settings_flag
|
||||
|
||||
def self.settings_option(key, &default)
|
||||
settings_method(key, key, &default)
|
||||
end
|
||||
private_class_method :settings_option
|
||||
|
||||
def self.settings_method(name, key, &default)
|
||||
define_method(name) do
|
||||
value = Bundler.settings[key]
|
||||
value = instance_eval(&default) if value.nil? && !default.nil?
|
||||
value
|
||||
end
|
||||
end
|
||||
private_class_method :settings_method
|
||||
|
||||
(1..10).each {|v| define_method("bundler_#{v}_mode?") { major_version >= v } }
|
||||
|
||||
settings_flag(:allow_bundler_dependency_conflicts) { bundler_2_mode? }
|
||||
settings_flag(:allow_offline_install) { bundler_2_mode? }
|
||||
settings_flag(:auto_clean_without_path) { bundler_2_mode? }
|
||||
settings_flag(:auto_config_jobs) { bundler_2_mode? }
|
||||
settings_flag(:cache_all) { bundler_2_mode? }
|
||||
settings_flag(:cache_command_is_package) { bundler_2_mode? }
|
||||
settings_flag(:console_command) { !bundler_2_mode? }
|
||||
settings_flag(:default_install_uses_path) { bundler_2_mode? }
|
||||
settings_flag(:deployment_means_frozen) { bundler_2_mode? }
|
||||
settings_flag(:disable_multisource) { bundler_2_mode? }
|
||||
settings_flag(:error_on_stderr) { bundler_2_mode? }
|
||||
settings_flag(:forget_cli_options) { bundler_2_mode? }
|
||||
settings_flag(:global_path_appends_ruby_scope) { bundler_2_mode? }
|
||||
settings_flag(:global_gem_cache) { bundler_2_mode? }
|
||||
settings_flag(:init_gems_rb) { bundler_2_mode? }
|
||||
settings_flag(:list_command) { bundler_2_mode? }
|
||||
settings_flag(:lockfile_uses_separate_rubygems_sources) { bundler_2_mode? }
|
||||
settings_flag(:only_update_to_newer_versions) { bundler_2_mode? }
|
||||
settings_flag(:path_relative_to_cwd) { bundler_2_mode? }
|
||||
settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
|
||||
settings_flag(:prefer_gems_rb) { bundler_2_mode? }
|
||||
settings_flag(:print_only_version_number) { bundler_2_mode? }
|
||||
settings_flag(:setup_makes_kernel_gem_public) { !bundler_2_mode? }
|
||||
settings_flag(:skip_default_git_sources) { bundler_2_mode? }
|
||||
settings_flag(:specific_platform) { bundler_2_mode? }
|
||||
settings_flag(:suppress_install_using_messages) { bundler_2_mode? }
|
||||
settings_flag(:unlock_source_unlocks_spec) { !bundler_2_mode? }
|
||||
settings_flag(:update_requires_all_flag) { bundler_2_mode? }
|
||||
settings_flag(:use_gem_version_promoter_for_major_updates) { bundler_2_mode? }
|
||||
settings_flag(:viz_command) { !bundler_2_mode? }
|
||||
|
||||
settings_option(:default_cli_command) { bundler_2_mode? ? :cli_help : :install }
|
||||
|
||||
def initialize(bundler_version)
|
||||
@bundler_version = Gem::Version.create(bundler_version)
|
||||
end
|
||||
|
||||
def major_version
|
||||
@bundler_version.segments.first
|
||||
end
|
||||
private :major_version
|
||||
end
|
||||
end
|
|
@ -0,0 +1,312 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/vendored_persistent"
|
||||
require "cgi"
|
||||
require "securerandom"
|
||||
require "zlib"
|
||||
|
||||
module Bundler
|
||||
# Handles all the fetching with the rubygems server
|
||||
class Fetcher
|
||||
autoload :CompactIndex, "bundler/fetcher/compact_index"
|
||||
autoload :Downloader, "bundler/fetcher/downloader"
|
||||
autoload :Dependency, "bundler/fetcher/dependency"
|
||||
autoload :Index, "bundler/fetcher/index"
|
||||
|
||||
# This error is raised when it looks like the network is down
|
||||
class NetworkDownError < HTTPError; end
|
||||
# This error is raised if the API returns a 413 (only printed in verbose)
|
||||
class FallbackError < HTTPError; end
|
||||
# This is the error raised if OpenSSL fails the cert verification
|
||||
class CertificateFailureError < HTTPError
|
||||
def initialize(remote_uri)
|
||||
remote_uri = filter_uri(remote_uri)
|
||||
super "Could not verify the SSL certificate for #{remote_uri}.\nThere" \
|
||||
" is a chance you are experiencing a man-in-the-middle attack, but" \
|
||||
" most likely your system doesn't have the CA certificates needed" \
|
||||
" for verification. For information about OpenSSL certificates, see" \
|
||||
" http://bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile" \
|
||||
" sources and change 'https' to 'http'."
|
||||
end
|
||||
end
|
||||
# This is the error raised when a source is HTTPS and OpenSSL didn't load
|
||||
class SSLError < HTTPError
|
||||
def initialize(msg = nil)
|
||||
super msg || "Could not load OpenSSL.\n" \
|
||||
"You must recompile Ruby with OpenSSL support or change the sources in your " \
|
||||
"Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL " \
|
||||
"using RVM are available at rvm.io/packages/openssl."
|
||||
end
|
||||
end
|
||||
# This error is raised if HTTP authentication is required, but not provided.
|
||||
class AuthenticationRequiredError < HTTPError
|
||||
def initialize(remote_uri)
|
||||
remote_uri = filter_uri(remote_uri)
|
||||
super "Authentication is required for #{remote_uri}.\n" \
|
||||
"Please supply credentials for this source. You can do this by running:\n" \
|
||||
" bundle config #{remote_uri} username:password"
|
||||
end
|
||||
end
|
||||
# This error is raised if HTTP authentication is provided, but incorrect.
|
||||
class BadAuthenticationError < HTTPError
|
||||
def initialize(remote_uri)
|
||||
remote_uri = filter_uri(remote_uri)
|
||||
super "Bad username or password for #{remote_uri}.\n" \
|
||||
"Please double-check your credentials and correct them."
|
||||
end
|
||||
end
|
||||
|
||||
# Exceptions classes that should bypass retry attempts. If your password didn't work the
|
||||
# first time, it's not going to the third time.
|
||||
NET_ERRORS = [:HTTPBadGateway, :HTTPBadRequest, :HTTPFailedDependency,
|
||||
:HTTPForbidden, :HTTPInsufficientStorage, :HTTPMethodNotAllowed,
|
||||
:HTTPMovedPermanently, :HTTPNoContent, :HTTPNotFound,
|
||||
:HTTPNotImplemented, :HTTPPreconditionFailed, :HTTPRequestEntityTooLarge,
|
||||
:HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity,
|
||||
:HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze
|
||||
FAIL_ERRORS = begin
|
||||
fail_errors = [AuthenticationRequiredError, BadAuthenticationError, FallbackError]
|
||||
fail_errors << Gem::Requirement::BadRequirementError if defined?(Gem::Requirement::BadRequirementError)
|
||||
fail_errors.concat(NET_ERRORS.map {|e| SharedHelpers.const_get_safely(e, Net) }.compact)
|
||||
end.freeze
|
||||
|
||||
class << self
|
||||
attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
|
||||
end
|
||||
|
||||
self.redirect_limit = Bundler.settings[:redirect] # How many redirects to allow in one request
|
||||
self.api_timeout = Bundler.settings[:timeout] # How long to wait for each API call
|
||||
self.max_retries = Bundler.settings[:retry] # How many retries for the API call
|
||||
|
||||
def initialize(remote)
|
||||
@remote = remote
|
||||
|
||||
Socket.do_not_reverse_lookup = true
|
||||
connection # create persistent connection
|
||||
end
|
||||
|
||||
def uri
|
||||
@remote.anonymized_uri
|
||||
end
|
||||
|
||||
# fetch a gem specification
|
||||
def fetch_spec(spec)
|
||||
spec -= [nil, "ruby", ""]
|
||||
spec_file_name = "#{spec.join "-"}.gemspec"
|
||||
|
||||
uri = URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
|
||||
if uri.scheme == "file"
|
||||
Bundler.load_marshal Bundler.rubygems.inflate(Gem.read_binary(uri.path))
|
||||
elsif cached_spec_path = gemspec_cached_path(spec_file_name)
|
||||
Bundler.load_gemspec(cached_spec_path)
|
||||
else
|
||||
Bundler.load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body)
|
||||
end
|
||||
rescue MarshalError
|
||||
raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
|
||||
"Your network or your gem server is probably having issues right now."
|
||||
end
|
||||
|
||||
# return the specs in the bundler format as an index with retries
|
||||
def specs_with_retry(gem_names, source)
|
||||
Bundler::Retry.new("fetcher", FAIL_ERRORS).attempts do
|
||||
specs(gem_names, source)
|
||||
end
|
||||
end
|
||||
|
||||
# return the specs in the bundler format as an index
|
||||
def specs(gem_names, source)
|
||||
old = Bundler.rubygems.sources
|
||||
index = Bundler::Index.new
|
||||
|
||||
if Bundler::Fetcher.disable_endpoint
|
||||
@use_api = false
|
||||
specs = fetchers.last.specs(gem_names)
|
||||
else
|
||||
specs = []
|
||||
fetchers.shift until fetchers.first.available? || fetchers.empty?
|
||||
fetchers.dup.each do |f|
|
||||
break unless f.api_fetcher? && !gem_names || !specs = f.specs(gem_names)
|
||||
fetchers.delete(f)
|
||||
end
|
||||
@use_api = false if fetchers.none?(&:api_fetcher?)
|
||||
end
|
||||
|
||||
specs.each do |name, version, platform, dependencies, metadata|
|
||||
next if name == "bundler"
|
||||
spec = if dependencies
|
||||
EndpointSpecification.new(name, version, platform, dependencies, metadata)
|
||||
else
|
||||
RemoteSpecification.new(name, version, platform, self)
|
||||
end
|
||||
spec.source = source
|
||||
spec.remote = @remote
|
||||
index << spec
|
||||
end
|
||||
|
||||
index
|
||||
rescue CertificateFailureError
|
||||
Bundler.ui.info "" if gem_names && use_api # newline after dots
|
||||
raise
|
||||
ensure
|
||||
Bundler.rubygems.sources = old
|
||||
end
|
||||
|
||||
def use_api
|
||||
return @use_api if defined?(@use_api)
|
||||
|
||||
fetchers.shift until fetchers.first.available?
|
||||
|
||||
@use_api = if remote_uri.scheme == "file" || Bundler::Fetcher.disable_endpoint
|
||||
false
|
||||
else
|
||||
fetchers.first.api_fetcher?
|
||||
end
|
||||
end
|
||||
|
||||
def user_agent
|
||||
@user_agent ||= begin
|
||||
ruby = Bundler::RubyVersion.system
|
||||
|
||||
agent = String.new("bundler/#{Bundler::VERSION}")
|
||||
agent << " rubygems/#{Gem::VERSION}"
|
||||
agent << " ruby/#{ruby.versions_string(ruby.versions)}"
|
||||
agent << " (#{ruby.host})"
|
||||
agent << " command/#{ARGV.first}"
|
||||
|
||||
if ruby.engine != "ruby"
|
||||
# engine_version raises on unknown engines
|
||||
engine_version = begin
|
||||
ruby.engine_versions
|
||||
rescue RuntimeError
|
||||
"???"
|
||||
end
|
||||
agent << " #{ruby.engine}/#{ruby.versions_string(engine_version)}"
|
||||
end
|
||||
|
||||
agent << " options/#{Bundler.settings.all.join(",")}"
|
||||
|
||||
agent << " ci/#{cis.join(",")}" if cis.any?
|
||||
|
||||
# add a random ID so we can consolidate runs server-side
|
||||
agent << " " << SecureRandom.hex(8)
|
||||
|
||||
# add any user agent strings set in the config
|
||||
extra_ua = Bundler.settings[:user_agent]
|
||||
agent << " " << extra_ua if extra_ua
|
||||
|
||||
agent
|
||||
end
|
||||
end
|
||||
|
||||
def fetchers
|
||||
@fetchers ||= FETCHERS.map {|f| f.new(downloader, @remote, uri) }
|
||||
end
|
||||
|
||||
def http_proxy
|
||||
return unless uri = connection.proxy_uri
|
||||
uri.to_s
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:0x#{object_id} uri=#{uri}>"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
FETCHERS = [CompactIndex, Dependency, Index].freeze
|
||||
|
||||
def cis
|
||||
env_cis = {
|
||||
"TRAVIS" => "travis",
|
||||
"CIRCLECI" => "circle",
|
||||
"SEMAPHORE" => "semaphore",
|
||||
"JENKINS_URL" => "jenkins",
|
||||
"BUILDBOX" => "buildbox",
|
||||
"GO_SERVER_URL" => "go",
|
||||
"SNAP_CI" => "snap",
|
||||
"CI_NAME" => ENV["CI_NAME"],
|
||||
"CI" => "ci"
|
||||
}
|
||||
env_cis.find_all {|env, _| ENV[env] }.map {|_, ci| ci }
|
||||
end
|
||||
|
||||
def connection
|
||||
@connection ||= begin
|
||||
needs_ssl = remote_uri.scheme == "https" ||
|
||||
Bundler.settings[:ssl_verify_mode] ||
|
||||
Bundler.settings[:ssl_client_cert]
|
||||
raise SSLError if needs_ssl && !defined?(OpenSSL::SSL)
|
||||
|
||||
con = PersistentHTTP.new "bundler", :ENV
|
||||
if gem_proxy = Bundler.rubygems.configuration[:http_proxy]
|
||||
con.proxy = URI.parse(gem_proxy) if gem_proxy != :no_proxy
|
||||
end
|
||||
|
||||
if remote_uri.scheme == "https"
|
||||
con.verify_mode = (Bundler.settings[:ssl_verify_mode] ||
|
||||
OpenSSL::SSL::VERIFY_PEER)
|
||||
con.cert_store = bundler_cert_store
|
||||
end
|
||||
|
||||
ssl_client_cert = Bundler.settings[:ssl_client_cert] ||
|
||||
(Bundler.rubygems.configuration.ssl_client_cert if
|
||||
Bundler.rubygems.configuration.respond_to?(:ssl_client_cert))
|
||||
if ssl_client_cert
|
||||
pem = File.read(ssl_client_cert)
|
||||
con.cert = OpenSSL::X509::Certificate.new(pem)
|
||||
con.key = OpenSSL::PKey::RSA.new(pem)
|
||||
end
|
||||
|
||||
con.read_timeout = Fetcher.api_timeout
|
||||
con.open_timeout = Fetcher.api_timeout
|
||||
con.override_headers["User-Agent"] = user_agent
|
||||
con.override_headers["X-Gemfile-Source"] = @remote.original_uri.to_s if @remote.original_uri
|
||||
con
|
||||
end
|
||||
end
|
||||
|
||||
# cached gem specification path, if one exists
|
||||
def gemspec_cached_path(spec_file_name)
|
||||
paths = Bundler.rubygems.spec_cache_dirs.map {|dir| File.join(dir, spec_file_name) }
|
||||
paths = paths.select {|path| File.file? path }
|
||||
paths.first
|
||||
end
|
||||
|
||||
HTTP_ERRORS = [
|
||||
Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH,
|
||||
Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
|
||||
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
||||
PersistentHTTP::Error, Zlib::BufError, Errno::EHOSTUNREACH
|
||||
].freeze
|
||||
|
||||
def bundler_cert_store
|
||||
store = OpenSSL::X509::Store.new
|
||||
ssl_ca_cert = Bundler.settings[:ssl_ca_cert] ||
|
||||
(Bundler.rubygems.configuration.ssl_ca_cert if
|
||||
Bundler.rubygems.configuration.respond_to?(:ssl_ca_cert))
|
||||
if ssl_ca_cert
|
||||
if File.directory? ssl_ca_cert
|
||||
store.add_path ssl_ca_cert
|
||||
else
|
||||
store.add_file ssl_ca_cert
|
||||
end
|
||||
else
|
||||
store.set_default_paths
|
||||
certs = File.expand_path("../ssl_certs/*/*.pem", __FILE__)
|
||||
Dir.glob(certs).each {|c| store.add_file c }
|
||||
end
|
||||
store
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remote_uri
|
||||
@remote.uri
|
||||
end
|
||||
|
||||
def downloader
|
||||
@downloader ||= Downloader.new(connection, self.class.redirect_limit)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Fetcher
|
||||
class Base
|
||||
attr_reader :downloader
|
||||
attr_reader :display_uri
|
||||
attr_reader :remote
|
||||
|
||||
def initialize(downloader, remote, display_uri)
|
||||
raise "Abstract class" if self.class == Base
|
||||
@downloader = downloader
|
||||
@remote = remote
|
||||
@display_uri = display_uri
|
||||
end
|
||||
|
||||
def remote_uri
|
||||
@remote.uri
|
||||
end
|
||||
|
||||
def fetch_uri
|
||||
@fetch_uri ||= begin
|
||||
if remote_uri.host == "rubygems.org"
|
||||
uri = remote_uri.dup
|
||||
uri.host = "index.rubygems.org"
|
||||
uri
|
||||
else
|
||||
remote_uri
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def available?
|
||||
true
|
||||
end
|
||||
|
||||
def api_fetcher?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_specs(debug_msg)
|
||||
if Bundler.ui.debug?
|
||||
Bundler.ui.debug debug_msg
|
||||
else
|
||||
Bundler.ui.info ".", false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,126 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/fetcher/base"
|
||||
require "bundler/worker"
|
||||
|
||||
module Bundler
|
||||
autoload :CompactIndexClient, "bundler/compact_index_client"
|
||||
|
||||
class Fetcher
|
||||
class CompactIndex < Base
|
||||
def self.compact_index_request(method_name)
|
||||
method = instance_method(method_name)
|
||||
undef_method(method_name)
|
||||
define_method(method_name) do |*args, &blk|
|
||||
begin
|
||||
method.bind(self).call(*args, &blk)
|
||||
rescue NetworkDownError, CompactIndexClient::Updater::MisMatchedChecksumError => e
|
||||
raise HTTPError, e.message
|
||||
rescue AuthenticationRequiredError
|
||||
# Fail since we got a 401 from the server.
|
||||
raise
|
||||
rescue HTTPError => e
|
||||
Bundler.ui.trace(e)
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def specs(gem_names)
|
||||
specs_for_names(gem_names)
|
||||
end
|
||||
compact_index_request :specs
|
||||
|
||||
def specs_for_names(gem_names)
|
||||
gem_info = []
|
||||
complete_gems = []
|
||||
remaining_gems = gem_names.dup
|
||||
|
||||
until remaining_gems.empty?
|
||||
log_specs "Looking up gems #{remaining_gems.inspect}"
|
||||
|
||||
deps = compact_index_client.dependencies(remaining_gems)
|
||||
next_gems = deps.map {|d| d[3].map(&:first).flatten(1) }.flatten(1).uniq
|
||||
deps.each {|dep| gem_info << dep }
|
||||
complete_gems.concat(deps.map(&:first)).uniq!
|
||||
remaining_gems = next_gems - complete_gems
|
||||
end
|
||||
@bundle_worker.stop if @bundle_worker
|
||||
@bundle_worker = nil # reset it. Not sure if necessary
|
||||
|
||||
gem_info
|
||||
end
|
||||
|
||||
def fetch_spec(spec)
|
||||
spec -= [nil, "ruby", ""]
|
||||
contents = compact_index_client.spec(*spec)
|
||||
return nil if contents.nil?
|
||||
contents.unshift(spec.first)
|
||||
contents[3].map! {|d| Gem::Dependency.new(*d) }
|
||||
EndpointSpecification.new(*contents)
|
||||
end
|
||||
compact_index_request :fetch_spec
|
||||
|
||||
def available?
|
||||
return nil unless SharedHelpers.md5_available?
|
||||
user_home = Bundler.user_home
|
||||
return nil unless user_home.directory? && user_home.writable?
|
||||
# Read info file checksums out of /versions, so we can know if gems are up to date
|
||||
fetch_uri.scheme != "file" && compact_index_client.update_and_parse_checksums!
|
||||
rescue CompactIndexClient::Updater::MisMatchedChecksumError => e
|
||||
Bundler.ui.debug(e.message)
|
||||
nil
|
||||
end
|
||||
compact_index_request :available?
|
||||
|
||||
def api_fetcher?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compact_index_client
|
||||
@compact_index_client ||= begin
|
||||
SharedHelpers.filesystem_access(cache_path) do
|
||||
CompactIndexClient.new(cache_path, client_fetcher)
|
||||
end.tap do |client|
|
||||
client.in_parallel = lambda do |inputs, &blk|
|
||||
func = lambda {|object, _index| blk.call(object) }
|
||||
worker = bundle_worker(func)
|
||||
inputs.each {|input| worker.enq(input) }
|
||||
inputs.map { worker.deq }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def bundle_worker(func = nil)
|
||||
@bundle_worker ||= begin
|
||||
worker_name = "Compact Index (#{display_uri.host})"
|
||||
Bundler::Worker.new(Bundler.current_ruby.rbx? ? 1 : 25, worker_name, func)
|
||||
end
|
||||
@bundle_worker.tap do |worker|
|
||||
worker.instance_variable_set(:@func, func) if func
|
||||
end
|
||||
end
|
||||
|
||||
def cache_path
|
||||
Bundler.user_cache.join("compact_index", remote.cache_slug)
|
||||
end
|
||||
|
||||
def client_fetcher
|
||||
ClientFetcher.new(self, Bundler.ui)
|
||||
end
|
||||
|
||||
ClientFetcher = Struct.new(:fetcher, :ui) do
|
||||
def call(path, headers)
|
||||
fetcher.downloader.fetch(fetcher.fetch_uri + path, headers)
|
||||
rescue NetworkDownError => e
|
||||
raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"]
|
||||
ui.warn "Using the cached data for the new index because of a network error: #{e}"
|
||||
Net::HTTPNotModified.new(nil, nil, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,82 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/fetcher/base"
|
||||
require "cgi"
|
||||
|
||||
module Bundler
|
||||
class Fetcher
|
||||
class Dependency < Base
|
||||
def available?
|
||||
@available ||= fetch_uri.scheme != "file" && downloader.fetch(dependency_api_uri)
|
||||
rescue NetworkDownError => e
|
||||
raise HTTPError, e.message
|
||||
rescue AuthenticationRequiredError
|
||||
# Fail since we got a 401 from the server.
|
||||
raise
|
||||
rescue HTTPError
|
||||
false
|
||||
end
|
||||
|
||||
def api_fetcher?
|
||||
true
|
||||
end
|
||||
|
||||
def specs(gem_names, full_dependency_list = [], last_spec_list = [])
|
||||
query_list = gem_names.uniq - full_dependency_list
|
||||
|
||||
log_specs "Query List: #{query_list.inspect}"
|
||||
|
||||
return last_spec_list if query_list.empty?
|
||||
|
||||
spec_list, deps_list = Bundler::Retry.new("dependency api", FAIL_ERRORS).attempts do
|
||||
dependency_specs(query_list)
|
||||
end
|
||||
|
||||
returned_gems = spec_list.map(&:first).uniq
|
||||
specs(deps_list, full_dependency_list + returned_gems, spec_list + last_spec_list)
|
||||
rescue MarshalError
|
||||
Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
|
||||
Bundler.ui.debug "could not fetch from the dependency API, trying the full index"
|
||||
nil
|
||||
rescue HTTPError, GemspecError
|
||||
Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
|
||||
Bundler.ui.debug "could not fetch from the dependency API\nit's suggested to retry using the full index via `bundle install --full-index`"
|
||||
nil
|
||||
end
|
||||
|
||||
def dependency_specs(gem_names)
|
||||
Bundler.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(",")}"
|
||||
|
||||
gem_list = unmarshalled_dep_gems(gem_names)
|
||||
get_formatted_specs_and_deps(gem_list)
|
||||
end
|
||||
|
||||
def unmarshalled_dep_gems(gem_names)
|
||||
gem_list = []
|
||||
gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names|
|
||||
marshalled_deps = downloader.fetch(dependency_api_uri(names)).body
|
||||
gem_list.concat(Bundler.load_marshal(marshalled_deps))
|
||||
end
|
||||
gem_list
|
||||
end
|
||||
|
||||
def get_formatted_specs_and_deps(gem_list)
|
||||
deps_list = []
|
||||
spec_list = []
|
||||
|
||||
gem_list.each do |s|
|
||||
deps_list.concat(s[:dependencies].map(&:first))
|
||||
deps = s[:dependencies].map {|n, d| [n, d.split(", ")] }
|
||||
spec_list.push([s[:name], s[:number], s[:platform], deps])
|
||||
end
|
||||
[spec_list, deps_list]
|
||||
end
|
||||
|
||||
def dependency_api_uri(gem_names = [])
|
||||
uri = fetch_uri + "api/v1/dependencies"
|
||||
uri.query = "gems=#{CGI.escape(gem_names.sort.join(","))}" if gem_names.any?
|
||||
uri
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,84 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Fetcher
|
||||
class Downloader
|
||||
attr_reader :connection
|
||||
attr_reader :redirect_limit
|
||||
|
||||
def initialize(connection, redirect_limit)
|
||||
@connection = connection
|
||||
@redirect_limit = redirect_limit
|
||||
end
|
||||
|
||||
def fetch(uri, headers = {}, counter = 0)
|
||||
raise HTTPError, "Too many redirects" if counter >= redirect_limit
|
||||
|
||||
response = request(uri, headers)
|
||||
Bundler.ui.debug("HTTP #{response.code} #{response.message} #{uri}")
|
||||
|
||||
case response
|
||||
when Net::HTTPSuccess, Net::HTTPNotModified
|
||||
response
|
||||
when Net::HTTPRedirection
|
||||
new_uri = URI.parse(response["location"])
|
||||
if new_uri.host == uri.host
|
||||
new_uri.user = uri.user
|
||||
new_uri.password = uri.password
|
||||
end
|
||||
fetch(new_uri, headers, counter + 1)
|
||||
when Net::HTTPRequestedRangeNotSatisfiable
|
||||
new_headers = headers.dup
|
||||
new_headers.delete("Range")
|
||||
new_headers["Accept-Encoding"] = "gzip"
|
||||
fetch(uri, new_headers)
|
||||
when Net::HTTPRequestEntityTooLarge
|
||||
raise FallbackError, response.body
|
||||
when Net::HTTPUnauthorized
|
||||
raise AuthenticationRequiredError, uri.host
|
||||
when Net::HTTPNotFound
|
||||
raise FallbackError, "Net::HTTPNotFound"
|
||||
else
|
||||
raise HTTPError, "#{response.class}#{": #{response.body}" unless response.body.empty?}"
|
||||
end
|
||||
end
|
||||
|
||||
def request(uri, headers)
|
||||
validate_uri_scheme!(uri)
|
||||
|
||||
Bundler.ui.debug "HTTP GET #{uri}"
|
||||
req = Net::HTTP::Get.new uri.request_uri, headers
|
||||
if uri.user
|
||||
user = CGI.unescape(uri.user)
|
||||
password = uri.password ? CGI.unescape(uri.password) : nil
|
||||
req.basic_auth(user, password)
|
||||
end
|
||||
connection.request(uri, req)
|
||||
rescue NoMethodError => e
|
||||
raise unless ["undefined method", "use_ssl="].all? {|snippet| e.message.include? snippet }
|
||||
raise LoadError.new("cannot load such file -- openssl")
|
||||
rescue OpenSSL::SSL::SSLError
|
||||
raise CertificateFailureError.new(uri)
|
||||
rescue *HTTP_ERRORS => e
|
||||
Bundler.ui.trace e
|
||||
case e.message
|
||||
when /host down:/, /getaddrinfo: nodename nor servname provided/
|
||||
raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \
|
||||
"connection and try again."
|
||||
else
|
||||
raise HTTPError, "Network error while fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" \
|
||||
" (#{e})"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_uri_scheme!(uri)
|
||||
return if uri.scheme =~ /\Ahttps?\z/
|
||||
raise InvalidOption,
|
||||
"The request uri `#{uri}` has an invalid scheme (`#{uri.scheme}`). " \
|
||||
"Did you mean `http` or `https`?"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/fetcher/base"
|
||||
require "rubygems/remote_fetcher"
|
||||
|
||||
module Bundler
|
||||
class Fetcher
|
||||
class Index < Base
|
||||
def specs(_gem_names)
|
||||
Bundler.rubygems.fetch_all_remote_specs(remote)
|
||||
rescue Gem::RemoteFetcher::FetchError, OpenSSL::SSL::SSLError, Net::HTTPFatalError => e
|
||||
case e.message
|
||||
when /certificate verify failed/
|
||||
raise CertificateFailureError.new(display_uri)
|
||||
when /401/
|
||||
raise AuthenticationRequiredError, remote_uri
|
||||
when /403/
|
||||
raise BadAuthenticationError, remote_uri if remote_uri.userinfo
|
||||
raise AuthenticationRequiredError, remote_uri
|
||||
else
|
||||
Bundler.ui.trace e
|
||||
raise HTTPError, "Could not fetch specs from #{display_uri}"
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_spec(spec)
|
||||
spec -= [nil, "ruby", ""]
|
||||
spec_file_name = "#{spec.join "-"}.gemspec"
|
||||
|
||||
uri = URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
|
||||
if uri.scheme == "file"
|
||||
Bundler.load_marshal Bundler.rubygems.inflate(Gem.read_binary(uri.path))
|
||||
elsif cached_spec_path = gemspec_cached_path(spec_file_name)
|
||||
Bundler.load_gemspec(cached_spec_path)
|
||||
else
|
||||
Bundler.load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body)
|
||||
end
|
||||
rescue MarshalError
|
||||
raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
|
||||
"Your network or your gem server is probably having issues right now."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# cached gem specification path, if one exists
|
||||
def gemspec_cached_path(spec_file_name)
|
||||
paths = Bundler.rubygems.spec_cache_dirs.map {|dir| File.join(dir, spec_file_name) }
|
||||
paths.find {|path| File.file? path }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,131 @@
|
|||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cgi"
|
||||
require "bundler/vendored_thor"
|
||||
|
||||
module Bundler
|
||||
module FriendlyErrors
|
||||
module_function
|
||||
|
||||
def log_error(error)
|
||||
case error
|
||||
when YamlSyntaxError
|
||||
Bundler.ui.error error.message
|
||||
Bundler.ui.trace error.orig_exception
|
||||
when Dsl::DSLError, GemspecError
|
||||
Bundler.ui.error error.message
|
||||
when GemRequireError
|
||||
Bundler.ui.error error.message
|
||||
Bundler.ui.trace error.orig_exception, nil, true
|
||||
when BundlerError
|
||||
Bundler.ui.error error.message, :wrap => true
|
||||
Bundler.ui.trace error
|
||||
when Thor::Error
|
||||
Bundler.ui.error error.message
|
||||
when LoadError
|
||||
raise error unless error.message =~ /cannot load such file -- openssl|openssl.so|libcrypto.so/
|
||||
Bundler.ui.error "\nCould not load OpenSSL."
|
||||
Bundler.ui.warn <<-WARN, :wrap => true
|
||||
You must recompile Ruby with OpenSSL support or change the sources in your \
|
||||
Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL \
|
||||
using RVM are available at http://rvm.io/packages/openssl.
|
||||
WARN
|
||||
Bundler.ui.trace error
|
||||
when Interrupt
|
||||
Bundler.ui.error "\nQuitting..."
|
||||
Bundler.ui.trace error
|
||||
when Gem::InvalidSpecificationException
|
||||
Bundler.ui.error error.message, :wrap => true
|
||||
when SystemExit
|
||||
when *[defined?(Java::JavaLang::OutOfMemoryError) && Java::JavaLang::OutOfMemoryError].compact
|
||||
Bundler.ui.error "\nYour JVM has run out of memory, and Bundler cannot continue. " \
|
||||
"You can decrease the amount of memory Bundler needs by removing gems from your Gemfile, " \
|
||||
"especially large gems. (Gems can be as large as hundreds of megabytes, and Bundler has to read those files!). " \
|
||||
"Alternatively, you can increase the amount of memory the JVM is able to use by running Bundler with jruby -J-Xmx1024m -S bundle (JRuby defaults to 500MB)."
|
||||
else request_issue_report_for(error)
|
||||
end
|
||||
rescue
|
||||
raise error
|
||||
end
|
||||
|
||||
def exit_status(error)
|
||||
case error
|
||||
when BundlerError then error.status_code
|
||||
when Thor::Error then 15
|
||||
when SystemExit then error.status
|
||||
else 1
|
||||
end
|
||||
end
|
||||
|
||||
def request_issue_report_for(e)
|
||||
Bundler.ui.info <<-EOS.gsub(/^ {8}/, "")
|
||||
--- ERROR REPORT TEMPLATE -------------------------------------------------------
|
||||
# Error Report
|
||||
|
||||
## Questions
|
||||
|
||||
Please fill out answers to these questions, it'll help us figure out
|
||||
why things are going wrong.
|
||||
|
||||
- **What did you do?**
|
||||
|
||||
I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}`
|
||||
|
||||
- **What did you expect to happen?**
|
||||
|
||||
I expected Bundler to...
|
||||
|
||||
- **What happened instead?**
|
||||
|
||||
Instead, what happened was...
|
||||
|
||||
- **Have you tried any solutions posted on similar issues in our issue tracker, stack overflow, or google?**
|
||||
|
||||
I tried...
|
||||
|
||||
- **Have you read our issues document, https://github.com/bundler/bundler/blob/master/doc/contributing/ISSUES.md?**
|
||||
|
||||
...
|
||||
|
||||
## Backtrace
|
||||
|
||||
```
|
||||
#{e.class}: #{e.message}
|
||||
#{e.backtrace && e.backtrace.join("\n ").chomp}
|
||||
```
|
||||
|
||||
#{Bundler::Env.report}
|
||||
--- TEMPLATE END ----------------------------------------------------------------
|
||||
|
||||
EOS
|
||||
|
||||
Bundler.ui.error "Unfortunately, an unexpected error occurred, and Bundler cannot continue."
|
||||
|
||||
Bundler.ui.warn <<-EOS.gsub(/^ {8}/, "")
|
||||
|
||||
First, try this link to see if there are any existing issue reports for this error:
|
||||
#{issues_url(e)}
|
||||
|
||||
If there aren't any reports for this error yet, please create copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at:
|
||||
https://github.com/bundler/bundler/issues/new
|
||||
EOS
|
||||
end
|
||||
|
||||
def issues_url(exception)
|
||||
message = exception.message.lines.first.tr(":", " ").chomp
|
||||
message = message.split("-").first if exception.is_a?(Errno)
|
||||
"https://github.com/bundler/bundler/search?q=" \
|
||||
"#{CGI.escape(message)}&type=Issues"
|
||||
end
|
||||
end
|
||||
|
||||
def self.with_friendly_errors
|
||||
yield
|
||||
rescue SignalException
|
||||
raise
|
||||
rescue Exception => e
|
||||
FriendlyErrors.log_error(e)
|
||||
exit FriendlyErrors.exit_status(e)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,202 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/vendored_thor" unless defined?(Thor)
|
||||
require "bundler"
|
||||
|
||||
module Bundler
|
||||
class GemHelper
|
||||
include Rake::DSL if defined? Rake::DSL
|
||||
|
||||
class << self
|
||||
# set when install'd.
|
||||
attr_accessor :instance
|
||||
|
||||
def install_tasks(opts = {})
|
||||
new(opts[:dir], opts[:name]).install
|
||||
end
|
||||
|
||||
def gemspec(&block)
|
||||
gemspec = instance.gemspec
|
||||
block.call(gemspec) if block
|
||||
gemspec
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :spec_path, :base, :gemspec
|
||||
|
||||
def initialize(base = nil, name = nil)
|
||||
Bundler.ui = UI::Shell.new
|
||||
@base = (base ||= SharedHelpers.pwd)
|
||||
gemspecs = name ? [File.join(base, "#{name}.gemspec")] : Dir[File.join(base, "{,*}.gemspec")]
|
||||
raise "Unable to determine name from existing gemspec. Use :name => 'gemname' in #install_tasks to manually set it." unless gemspecs.size == 1
|
||||
@spec_path = gemspecs.first
|
||||
@gemspec = Bundler.load_gemspec(@spec_path)
|
||||
end
|
||||
|
||||
def install
|
||||
built_gem_path = nil
|
||||
|
||||
desc "Build #{name}-#{version}.gem into the pkg directory."
|
||||
task "build" do
|
||||
built_gem_path = build_gem
|
||||
end
|
||||
|
||||
desc "Build and install #{name}-#{version}.gem into system gems."
|
||||
task "install" => "build" do
|
||||
install_gem(built_gem_path)
|
||||
end
|
||||
|
||||
desc "Build and install #{name}-#{version}.gem into system gems without network access."
|
||||
task "install:local" => "build" do
|
||||
install_gem(built_gem_path, :local)
|
||||
end
|
||||
|
||||
desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to #{gem_push_host}\n" \
|
||||
"To prevent publishing in RubyGems use `gem_push=no rake release`"
|
||||
task "release", [:remote] => ["build", "release:guard_clean",
|
||||
"release:source_control_push", "release:rubygem_push"] do
|
||||
end
|
||||
|
||||
task "release:guard_clean" do
|
||||
guard_clean
|
||||
end
|
||||
|
||||
task "release:source_control_push", [:remote] do |_, args|
|
||||
tag_version { git_push(args[:remote]) } unless already_tagged?
|
||||
end
|
||||
|
||||
task "release:rubygem_push" do
|
||||
rubygem_push(built_gem_path) if gem_push?
|
||||
end
|
||||
|
||||
GemHelper.instance = self
|
||||
end
|
||||
|
||||
def build_gem
|
||||
file_name = nil
|
||||
sh("gem build -V '#{spec_path}'") do
|
||||
file_name = File.basename(built_gem_path)
|
||||
SharedHelpers.filesystem_access(File.join(base, "pkg")) {|p| FileUtils.mkdir_p(p) }
|
||||
FileUtils.mv(built_gem_path, "pkg")
|
||||
Bundler.ui.confirm "#{name} #{version} built to pkg/#{file_name}."
|
||||
end
|
||||
File.join(base, "pkg", file_name)
|
||||
end
|
||||
|
||||
def install_gem(built_gem_path = nil, local = false)
|
||||
built_gem_path ||= build_gem
|
||||
out, _ = sh_with_code("gem install '#{built_gem_path}'#{" --local" if local}")
|
||||
raise "Couldn't install gem, run `gem install #{built_gem_path}' for more detailed output" unless out[/Successfully installed/]
|
||||
Bundler.ui.confirm "#{name} (#{version}) installed."
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def rubygem_push(path)
|
||||
gem_command = "gem push '#{path}'"
|
||||
gem_command += " --key #{gem_key}" if gem_key
|
||||
gem_command += " --host #{allowed_push_host}" if allowed_push_host
|
||||
unless allowed_push_host || Bundler.user_home.join(".gem/credentials").file?
|
||||
raise "Your rubygems.org credentials aren't set. Run `gem push` to set them."
|
||||
end
|
||||
sh(gem_command)
|
||||
Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_push_host}"
|
||||
end
|
||||
|
||||
def built_gem_path
|
||||
Dir[File.join(base, "#{name}-*.gem")].sort_by {|f| File.mtime(f) }.last
|
||||
end
|
||||
|
||||
def git_push(remote = "")
|
||||
perform_git_push remote
|
||||
perform_git_push "#{remote} --tags"
|
||||
Bundler.ui.confirm "Pushed git commits and tags."
|
||||
end
|
||||
|
||||
def allowed_push_host
|
||||
@gemspec.metadata["allowed_push_host"] if @gemspec.respond_to?(:metadata)
|
||||
end
|
||||
|
||||
def gem_push_host
|
||||
env_rubygems_host = ENV["RUBYGEMS_HOST"]
|
||||
env_rubygems_host = nil if
|
||||
env_rubygems_host && env_rubygems_host.empty?
|
||||
|
||||
allowed_push_host || env_rubygems_host || "rubygems.org"
|
||||
end
|
||||
|
||||
def perform_git_push(options = "")
|
||||
cmd = "git push #{options}"
|
||||
out, code = sh_with_code(cmd)
|
||||
raise "Couldn't git push. `#{cmd}' failed with the following output:\n\n#{out}\n" unless code == 0
|
||||
end
|
||||
|
||||
def already_tagged?
|
||||
return false unless sh("git tag").split(/\n/).include?(version_tag)
|
||||
Bundler.ui.confirm "Tag #{version_tag} has already been created."
|
||||
true
|
||||
end
|
||||
|
||||
def guard_clean
|
||||
clean? && committed? || raise("There are files that need to be committed first.")
|
||||
end
|
||||
|
||||
def clean?
|
||||
sh_with_code("git diff --exit-code")[1] == 0
|
||||
end
|
||||
|
||||
def committed?
|
||||
sh_with_code("git diff-index --quiet --cached HEAD")[1] == 0
|
||||
end
|
||||
|
||||
def tag_version
|
||||
sh "git tag -m \"Version #{version}\" #{version_tag}"
|
||||
Bundler.ui.confirm "Tagged #{version_tag}."
|
||||
yield if block_given?
|
||||
rescue RuntimeError
|
||||
Bundler.ui.error "Untagging #{version_tag} due to error."
|
||||
sh_with_code "git tag -d #{version_tag}"
|
||||
raise
|
||||
end
|
||||
|
||||
def version
|
||||
gemspec.version
|
||||
end
|
||||
|
||||
def version_tag
|
||||
"v#{version}"
|
||||
end
|
||||
|
||||
def name
|
||||
gemspec.name
|
||||
end
|
||||
|
||||
def sh(cmd, &block)
|
||||
out, code = sh_with_code(cmd, &block)
|
||||
unless code.zero?
|
||||
raise(out.empty? ? "Running `#{cmd}` failed. Run this command directly for more detailed output." : out)
|
||||
end
|
||||
out
|
||||
end
|
||||
|
||||
def sh_with_code(cmd, &block)
|
||||
cmd += " 2>&1"
|
||||
outbuf = String.new
|
||||
Bundler.ui.debug(cmd)
|
||||
SharedHelpers.chdir(base) do
|
||||
outbuf = `#{cmd}`
|
||||
status = $?.exitstatus
|
||||
block.call(outbuf) if status.zero? && block
|
||||
[outbuf, status]
|
||||
end
|
||||
end
|
||||
|
||||
def gem_key
|
||||
Bundler.settings["gem.push_key"].to_s.downcase if Bundler.settings["gem.push_key"]
|
||||
end
|
||||
|
||||
def gem_push?
|
||||
!%w[n no nil false off 0].include?(ENV["gem_push"].to_s.downcase)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,101 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module GemHelpers
|
||||
GENERIC_CACHE = {} # rubocop:disable MutableConstant
|
||||
GENERICS = [
|
||||
[Gem::Platform.new("java"), Gem::Platform.new("java")],
|
||||
[Gem::Platform.new("mswin32"), Gem::Platform.new("mswin32")],
|
||||
[Gem::Platform.new("mswin64"), Gem::Platform.new("mswin64")],
|
||||
[Gem::Platform.new("universal-mingw32"), Gem::Platform.new("universal-mingw32")],
|
||||
[Gem::Platform.new("x64-mingw32"), Gem::Platform.new("x64-mingw32")],
|
||||
[Gem::Platform.new("x86_64-mingw32"), Gem::Platform.new("x64-mingw32")],
|
||||
[Gem::Platform.new("mingw32"), Gem::Platform.new("x86-mingw32")]
|
||||
].freeze
|
||||
|
||||
def generic(p)
|
||||
return p if p == Gem::Platform::RUBY
|
||||
|
||||
GENERIC_CACHE[p] ||= begin
|
||||
_, found = GENERICS.find do |match, _generic|
|
||||
p.os == match.os && (!match.cpu || p.cpu == match.cpu)
|
||||
end
|
||||
found || Gem::Platform::RUBY
|
||||
end
|
||||
end
|
||||
module_function :generic
|
||||
|
||||
def generic_local_platform
|
||||
generic(Bundler.local_platform)
|
||||
end
|
||||
module_function :generic_local_platform
|
||||
|
||||
def platform_specificity_match(spec_platform, user_platform)
|
||||
spec_platform = Gem::Platform.new(spec_platform)
|
||||
return PlatformMatch::EXACT_MATCH if spec_platform == user_platform
|
||||
return PlatformMatch::WORST_MATCH if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
|
||||
|
||||
PlatformMatch.new(
|
||||
PlatformMatch.os_match(spec_platform, user_platform),
|
||||
PlatformMatch.cpu_match(spec_platform, user_platform),
|
||||
PlatformMatch.platform_version_match(spec_platform, user_platform)
|
||||
)
|
||||
end
|
||||
module_function :platform_specificity_match
|
||||
|
||||
def select_best_platform_match(specs, platform)
|
||||
specs.select {|spec| spec.match_platform(platform) }.
|
||||
min_by {|spec| platform_specificity_match(spec.platform, platform) }
|
||||
end
|
||||
module_function :select_best_platform_match
|
||||
|
||||
PlatformMatch = Struct.new(:os_match, :cpu_match, :platform_version_match)
|
||||
class PlatformMatch
|
||||
def <=>(other)
|
||||
return nil unless other.is_a?(PlatformMatch)
|
||||
|
||||
m = os_match <=> other.os_match
|
||||
return m unless m.zero?
|
||||
|
||||
m = cpu_match <=> other.cpu_match
|
||||
return m unless m.zero?
|
||||
|
||||
m = platform_version_match <=> other.platform_version_match
|
||||
m
|
||||
end
|
||||
|
||||
EXACT_MATCH = new(-1, -1, -1).freeze
|
||||
WORST_MATCH = new(1_000_000, 1_000_000, 1_000_000).freeze
|
||||
|
||||
def self.os_match(spec_platform, user_platform)
|
||||
if spec_platform.os == user_platform.os
|
||||
0
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
def self.cpu_match(spec_platform, user_platform)
|
||||
if spec_platform.cpu == user_platform.cpu
|
||||
0
|
||||
elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
|
||||
0
|
||||
elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
|
||||
1
|
||||
else
|
||||
2
|
||||
end
|
||||
end
|
||||
|
||||
def self.platform_version_match(spec_platform, user_platform)
|
||||
if spec_platform.version == user_platform.version
|
||||
0
|
||||
elsif spec_platform.version.nil?
|
||||
1
|
||||
else
|
||||
2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rubygems/remote_fetcher"
|
||||
|
||||
module Bundler
|
||||
# Adds support for setting custom HTTP headers when fetching gems from the
|
||||
# server.
|
||||
#
|
||||
# TODO: Get rid of this when and if gemstash only supports RubyGems versions
|
||||
# that contain https://github.com/rubygems/rubygems/commit/3db265cc20b2f813.
|
||||
class GemRemoteFetcher < Gem::RemoteFetcher
|
||||
attr_accessor :headers
|
||||
|
||||
# Extracted from RubyGems 2.4.
|
||||
def fetch_http(uri, last_modified = nil, head = false, depth = 0)
|
||||
fetch_type = head ? Net::HTTP::Head : Net::HTTP::Get
|
||||
# beginning of change
|
||||
response = request uri, fetch_type, last_modified do |req|
|
||||
headers.each {|k, v| req.add_field(k, v) } if headers
|
||||
end
|
||||
# end of change
|
||||
|
||||
case response
|
||||
when Net::HTTPOK, Net::HTTPNotModified then
|
||||
response.uri = uri if response.respond_to? :uri
|
||||
head ? response : response.body
|
||||
when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
|
||||
Net::HTTPTemporaryRedirect then
|
||||
raise FetchError.new("too many redirects", uri) if depth > 10
|
||||
|
||||
location = URI.parse response["Location"]
|
||||
|
||||
if https?(uri) && !https?(location)
|
||||
raise FetchError.new("redirecting to non-https resource: #{location}", uri)
|
||||
end
|
||||
|
||||
fetch_http(location, last_modified, head, depth + 1)
|
||||
else
|
||||
raise FetchError.new("bad response #{response.message} #{response.code}", uri)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rake/clean"
|
||||
CLOBBER.include "pkg"
|
||||
|
||||
require "bundler/gem_helper"
|
||||
Bundler::GemHelper.install_tasks
|
|
@ -0,0 +1,190 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# This class contains all of the logic for determining the next version of a
|
||||
# Gem to update to based on the requested level (patch, minor, major).
|
||||
# Primarily designed to work with Resolver which will provide it the list of
|
||||
# available dependency versions as found in its index, before returning it to
|
||||
# to the resolution engine to select the best version.
|
||||
class GemVersionPromoter
|
||||
DEBUG = ENV["DEBUG_RESOLVER"]
|
||||
|
||||
attr_reader :level, :locked_specs, :unlock_gems
|
||||
|
||||
# By default, strict is false, meaning every available version of a gem
|
||||
# is returned from sort_versions. The order gives preference to the
|
||||
# requested level (:patch, :minor, :major) but in complicated requirement
|
||||
# cases some gems will by necessity by promoted past the requested level,
|
||||
# or even reverted to older versions.
|
||||
#
|
||||
# If strict is set to true, the results from sort_versions will be
|
||||
# truncated, eliminating any version outside the current level scope.
|
||||
# This can lead to unexpected outcomes or even VersionConflict exceptions
|
||||
# that report a version of a gem not existing for versions that indeed do
|
||||
# existing in the referenced source.
|
||||
attr_accessor :strict
|
||||
|
||||
attr_accessor :prerelease_specified
|
||||
|
||||
# Given a list of locked_specs and a list of gems to unlock creates a
|
||||
# GemVersionPromoter instance.
|
||||
#
|
||||
# @param locked_specs [SpecSet] All current locked specs. Unlike Definition
|
||||
# where this list is empty if all gems are being updated, this should
|
||||
# always be populated for all gems so this class can properly function.
|
||||
# @param unlock_gems [String] List of gem names being unlocked. If empty,
|
||||
# all gems will be considered unlocked.
|
||||
# @return [GemVersionPromoter]
|
||||
def initialize(locked_specs = SpecSet.new([]), unlock_gems = [])
|
||||
@level = :major
|
||||
@strict = false
|
||||
@locked_specs = locked_specs
|
||||
@unlock_gems = unlock_gems
|
||||
@sort_versions = {}
|
||||
@prerelease_specified = {}
|
||||
end
|
||||
|
||||
# @param value [Symbol] One of three Symbols: :major, :minor or :patch.
|
||||
def level=(value)
|
||||
v = case value
|
||||
when String, Symbol
|
||||
value.to_sym
|
||||
end
|
||||
|
||||
raise ArgumentError, "Unexpected level #{v}. Must be :major, :minor or :patch" unless [:major, :minor, :patch].include?(v)
|
||||
@level = v
|
||||
end
|
||||
|
||||
# Given a Dependency and an Array of SpecGroups of available versions for a
|
||||
# gem, this method will return the Array of SpecGroups sorted (and possibly
|
||||
# truncated if strict is true) in an order to give preference to the current
|
||||
# level (:major, :minor or :patch) when resolution is deciding what versions
|
||||
# best resolve all dependencies in the bundle.
|
||||
# @param dep [Dependency] The Dependency of the gem.
|
||||
# @param spec_groups [SpecGroup] An array of SpecGroups for the same gem
|
||||
# named in the @dep param.
|
||||
# @return [SpecGroup] A new instance of the SpecGroup Array sorted and
|
||||
# possibly filtered.
|
||||
def sort_versions(dep, spec_groups)
|
||||
before_result = "before sort_versions: #{debug_format_result(dep, spec_groups).inspect}" if DEBUG
|
||||
|
||||
@sort_versions[dep] ||= begin
|
||||
gem_name = dep.name
|
||||
|
||||
# An Array per version returned, different entries for different platforms.
|
||||
# We only need the version here so it's ok to hard code this to the first instance.
|
||||
locked_spec = locked_specs[gem_name].first
|
||||
|
||||
if strict
|
||||
filter_dep_specs(spec_groups, locked_spec)
|
||||
else
|
||||
sort_dep_specs(spec_groups, locked_spec)
|
||||
end.tap do |specs|
|
||||
if DEBUG
|
||||
STDERR.puts before_result
|
||||
STDERR.puts " after sort_versions: #{debug_format_result(dep, specs).inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @return [bool] Convenience method for testing value of level variable.
|
||||
def major?
|
||||
level == :major
|
||||
end
|
||||
|
||||
# @return [bool] Convenience method for testing value of level variable.
|
||||
def minor?
|
||||
level == :minor
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_dep_specs(spec_groups, locked_spec)
|
||||
res = spec_groups.select do |spec_group|
|
||||
if locked_spec && !major?
|
||||
gsv = spec_group.version
|
||||
lsv = locked_spec.version
|
||||
|
||||
must_match = minor? ? [0] : [0, 1]
|
||||
|
||||
matches = must_match.map {|idx| gsv.segments[idx] == lsv.segments[idx] }
|
||||
(matches.uniq == [true]) ? (gsv >= lsv) : false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
sort_dep_specs(res, locked_spec)
|
||||
end
|
||||
|
||||
def sort_dep_specs(spec_groups, locked_spec)
|
||||
return spec_groups unless locked_spec
|
||||
@gem_name = locked_spec.name
|
||||
@locked_version = locked_spec.version
|
||||
|
||||
result = spec_groups.sort do |a, b|
|
||||
@a_ver = a.version
|
||||
@b_ver = b.version
|
||||
|
||||
unless @prerelease_specified[@gem_name]
|
||||
a_pre = @a_ver.prerelease?
|
||||
b_pre = @b_ver.prerelease?
|
||||
|
||||
next -1 if a_pre && !b_pre
|
||||
next 1 if b_pre && !a_pre
|
||||
end
|
||||
|
||||
if major?
|
||||
@a_ver <=> @b_ver
|
||||
elsif either_version_older_than_locked
|
||||
@a_ver <=> @b_ver
|
||||
elsif segments_do_not_match(:major)
|
||||
@b_ver <=> @a_ver
|
||||
elsif !minor? && segments_do_not_match(:minor)
|
||||
@b_ver <=> @a_ver
|
||||
else
|
||||
@a_ver <=> @b_ver
|
||||
end
|
||||
end
|
||||
post_sort(result)
|
||||
end
|
||||
|
||||
def either_version_older_than_locked
|
||||
@a_ver < @locked_version || @b_ver < @locked_version
|
||||
end
|
||||
|
||||
def segments_do_not_match(level)
|
||||
index = [:major, :minor].index(level)
|
||||
@a_ver.segments[index] != @b_ver.segments[index]
|
||||
end
|
||||
|
||||
def unlocking_gem?
|
||||
unlock_gems.empty? || unlock_gems.include?(@gem_name)
|
||||
end
|
||||
|
||||
# Specific version moves can't always reliably be done during sorting
|
||||
# as not all elements are compared against each other.
|
||||
def post_sort(result)
|
||||
# default :major behavior in Bundler does not do this
|
||||
return result if major?
|
||||
if unlocking_gem?
|
||||
result
|
||||
else
|
||||
move_version_to_end(result, @locked_version)
|
||||
end
|
||||
end
|
||||
|
||||
def move_version_to_end(result, version)
|
||||
move, keep = result.partition {|s| s.version.to_s == version.to_s }
|
||||
keep.concat(move)
|
||||
end
|
||||
|
||||
def debug_format_result(dep, spec_groups)
|
||||
a = [dep.to_s,
|
||||
spec_groups.map {|sg| [sg.version, sg.dependencies_for_activated_platforms.map {|dp| [dp.name, dp.requirement.to_s] }] }]
|
||||
last_map = a.last.map {|sg_data| [sg_data.first.version, sg_data.last.map {|aa| aa.join(" ") }] }
|
||||
[a.first, last_map, level, strict ? :strict : :not_strict]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Gemdeps
|
||||
def initialize(runtime)
|
||||
@runtime = runtime
|
||||
end
|
||||
|
||||
def requested_specs
|
||||
@runtime.requested_specs
|
||||
end
|
||||
|
||||
def specs
|
||||
@runtime.specs
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@runtime.dependencies
|
||||
end
|
||||
|
||||
def current_dependencies
|
||||
@runtime.current_dependencies
|
||||
end
|
||||
|
||||
def requires
|
||||
@runtime.requires
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,152 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "set"
|
||||
module Bundler
|
||||
class Graph
|
||||
GRAPH_NAME = :Gemfile
|
||||
|
||||
def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = [])
|
||||
@env = env
|
||||
@output_file = output_file
|
||||
@show_version = show_version
|
||||
@show_requirements = show_requirements
|
||||
@output_format = output_format
|
||||
@without_groups = without.map(&:to_sym)
|
||||
|
||||
@groups = []
|
||||
@relations = Hash.new {|h, k| h[k] = Set.new }
|
||||
@node_options = {}
|
||||
@edge_options = {}
|
||||
|
||||
_populate_relations
|
||||
end
|
||||
|
||||
attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format
|
||||
|
||||
def viz
|
||||
GraphVizClient.new(self).run
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _populate_relations
|
||||
parent_dependencies = _groups.values.to_set.flatten
|
||||
loop do
|
||||
break if parent_dependencies.empty?
|
||||
|
||||
tmp = Set.new
|
||||
parent_dependencies.each do |dependency|
|
||||
child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set
|
||||
@relations[dependency.name] += child_dependencies.map(&:name).to_set
|
||||
tmp += child_dependencies
|
||||
|
||||
@node_options[dependency.name] = _make_label(dependency, :node)
|
||||
child_dependencies.each do |c_dependency|
|
||||
@edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge)
|
||||
end
|
||||
end
|
||||
parent_dependencies = tmp
|
||||
end
|
||||
end
|
||||
|
||||
def _groups
|
||||
relations = Hash.new {|h, k| h[k] = Set.new }
|
||||
@env.current_dependencies.each do |dependency|
|
||||
dependency.groups.each do |group|
|
||||
next if @without_groups.include?(group)
|
||||
|
||||
relations[group.to_s].add(dependency)
|
||||
@relations[group.to_s].add(dependency.name)
|
||||
|
||||
@node_options[group.to_s] ||= _make_label(group, :node)
|
||||
@edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge)
|
||||
end
|
||||
end
|
||||
@groups = relations.keys
|
||||
relations
|
||||
end
|
||||
|
||||
def _make_label(symbol_or_string_or_dependency, element_type)
|
||||
case element_type.to_sym
|
||||
when :node
|
||||
if symbol_or_string_or_dependency.is_a?(Gem::Dependency)
|
||||
label = symbol_or_string_or_dependency.name.dup
|
||||
label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version
|
||||
else
|
||||
label = symbol_or_string_or_dependency.to_s
|
||||
end
|
||||
when :edge
|
||||
label = nil
|
||||
if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements
|
||||
tmp = symbol_or_string_or_dependency.requirements_list.join(", ")
|
||||
label = tmp if tmp != ">= 0"
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "2nd argument is invalid"
|
||||
end
|
||||
label.nil? ? {} : { :label => label }
|
||||
end
|
||||
|
||||
def spec_for_dependency(dependency)
|
||||
@env.requested_specs.find {|s| s.name == dependency.name }
|
||||
end
|
||||
|
||||
class GraphVizClient
|
||||
def initialize(graph_instance)
|
||||
@graph_name = graph_instance.class::GRAPH_NAME
|
||||
@groups = graph_instance.groups
|
||||
@relations = graph_instance.relations
|
||||
@node_options = graph_instance.node_options
|
||||
@edge_options = graph_instance.edge_options
|
||||
@output_file = graph_instance.output_file
|
||||
@output_format = graph_instance.output_format
|
||||
end
|
||||
|
||||
def g
|
||||
@g ||= ::GraphViz.digraph(@graph_name, :concentrate => true, :normalize => true, :nodesep => 0.55) do |g|
|
||||
g.edge[:weight] = 2
|
||||
g.edge[:fontname] = g.node[:fontname] = "Arial, Helvetica, SansSerif"
|
||||
g.edge[:fontsize] = 12
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
@groups.each do |group|
|
||||
g.add_nodes(
|
||||
group, {
|
||||
:style => "filled",
|
||||
:fillcolor => "#B9B9D5",
|
||||
:shape => "box3d",
|
||||
:fontsize => 16
|
||||
}.merge(@node_options[group])
|
||||
)
|
||||
end
|
||||
|
||||
@relations.each do |parent, children|
|
||||
children.each do |child|
|
||||
if @groups.include?(parent)
|
||||
g.add_nodes(child, { :style => "filled", :fillcolor => "#B9B9D5" }.merge(@node_options[child]))
|
||||
g.add_edges(parent, child, { :constraint => false }.merge(@edge_options["#{parent}_#{child}"]))
|
||||
else
|
||||
g.add_nodes(child, @node_options[child])
|
||||
g.add_edges(parent, child, @edge_options["#{parent}_#{child}"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if @output_format.to_s == "debug"
|
||||
$stdout.puts g.output :none => String
|
||||
Bundler.ui.info "debugging bundle viz..."
|
||||
else
|
||||
begin
|
||||
g.output @output_format.to_sym => "#{@output_file}.#{@output_format}"
|
||||
Bundler.ui.info "#{@output_file}.#{@output_format}"
|
||||
rescue ArgumentError => e
|
||||
$stderr.puts "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb"
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,213 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "set"
|
||||
|
||||
module Bundler
|
||||
class Index
|
||||
include Enumerable
|
||||
|
||||
def self.build
|
||||
i = new
|
||||
yield i
|
||||
i
|
||||
end
|
||||
|
||||
attr_reader :specs, :all_specs, :sources
|
||||
protected :specs, :all_specs
|
||||
|
||||
RUBY = "ruby".freeze
|
||||
NULL = "\0".freeze
|
||||
|
||||
def initialize
|
||||
@sources = []
|
||||
@cache = {}
|
||||
@specs = Hash.new {|h, k| h[k] = {} }
|
||||
@all_specs = Hash.new {|h, k| h[k] = EMPTY_SEARCH }
|
||||
end
|
||||
|
||||
def initialize_copy(o)
|
||||
@sources = o.sources.dup
|
||||
@cache = {}
|
||||
@specs = Hash.new {|h, k| h[k] = {} }
|
||||
@all_specs = Hash.new {|h, k| h[k] = EMPTY_SEARCH }
|
||||
|
||||
o.specs.each do |name, hash|
|
||||
@specs[name] = hash.dup
|
||||
end
|
||||
o.all_specs.each do |name, array|
|
||||
@all_specs[name] = array.dup
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:0x#{object_id} sources=#{sources.map(&:inspect)} specs.size=#{specs.size}>"
|
||||
end
|
||||
|
||||
def empty?
|
||||
each { return false }
|
||||
true
|
||||
end
|
||||
|
||||
def search_all(name)
|
||||
all_matches = local_search(name) + @all_specs[name]
|
||||
@sources.each do |source|
|
||||
all_matches.concat(source.search_all(name))
|
||||
end
|
||||
all_matches
|
||||
end
|
||||
|
||||
# Search this index's specs, and any source indexes that this index knows
|
||||
# about, returning all of the results.
|
||||
def search(query, base = nil)
|
||||
sort_specs(unsorted_search(query, base))
|
||||
end
|
||||
|
||||
def unsorted_search(query, base)
|
||||
results = local_search(query, base)
|
||||
|
||||
seen = results.map(&:full_name).to_set unless @sources.empty?
|
||||
|
||||
@sources.each do |source|
|
||||
source.unsorted_search(query, base).each do |spec|
|
||||
results << spec if seen.add?(spec.full_name)
|
||||
end
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
protected :unsorted_search
|
||||
|
||||
def self.sort_specs(specs)
|
||||
specs.sort_by do |s|
|
||||
platform_string = s.platform.to_s
|
||||
[s.version, platform_string == RUBY ? NULL : platform_string]
|
||||
end
|
||||
end
|
||||
|
||||
def sort_specs(specs)
|
||||
self.class.sort_specs(specs)
|
||||
end
|
||||
|
||||
def local_search(query, base = nil)
|
||||
case query
|
||||
when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query)
|
||||
when String then specs_by_name(query)
|
||||
when Gem::Dependency then search_by_dependency(query, base)
|
||||
when DepProxy then search_by_dependency(query.dep, base)
|
||||
else
|
||||
raise "You can't search for a #{query.inspect}."
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :[], :search
|
||||
|
||||
def <<(spec)
|
||||
@specs[spec.name][spec.full_name] = spec
|
||||
spec
|
||||
end
|
||||
|
||||
def each(&blk)
|
||||
return enum_for(:each) unless blk
|
||||
specs.values.each do |spec_sets|
|
||||
spec_sets.values.each(&blk)
|
||||
end
|
||||
sources.each {|s| s.each(&blk) }
|
||||
self
|
||||
end
|
||||
|
||||
def spec_names
|
||||
names = specs.keys + sources.map(&:spec_names)
|
||||
names.uniq!
|
||||
names
|
||||
end
|
||||
|
||||
# returns a list of the dependencies
|
||||
def unmet_dependency_names
|
||||
dependency_names.select do |name|
|
||||
name != "bundler" && search(name).empty?
|
||||
end
|
||||
end
|
||||
|
||||
def dependency_names
|
||||
names = []
|
||||
each do |spec|
|
||||
spec.dependencies.each do |dep|
|
||||
next if dep.type == :development
|
||||
names << dep.name
|
||||
end
|
||||
end
|
||||
names.uniq
|
||||
end
|
||||
|
||||
def use(other, override_dupes = false)
|
||||
return unless other
|
||||
other.each do |s|
|
||||
if (dupes = search_by_spec(s)) && !dupes.empty?
|
||||
# safe to << since it's a new array when it has contents
|
||||
@all_specs[s.name] = dupes << s
|
||||
next unless override_dupes
|
||||
end
|
||||
self << s
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def size
|
||||
@sources.inject(@specs.size) do |size, source|
|
||||
size += source.size
|
||||
end
|
||||
end
|
||||
|
||||
# Whether all the specs in self are in other
|
||||
# TODO: rename to #include?
|
||||
def ==(other)
|
||||
all? do |spec|
|
||||
other_spec = other[spec].first
|
||||
other_spec && dependencies_eql?(spec, other_spec) && spec.source == other_spec.source
|
||||
end
|
||||
end
|
||||
|
||||
def dependencies_eql?(spec, other_spec)
|
||||
deps = spec.dependencies.select {|d| d.type != :development }
|
||||
other_deps = other_spec.dependencies.select {|d| d.type != :development }
|
||||
Set.new(deps) == Set.new(other_deps)
|
||||
end
|
||||
|
||||
def add_source(index)
|
||||
raise ArgumentError, "Source must be an index, not #{index.class}" unless index.is_a?(Index)
|
||||
@sources << index
|
||||
@sources.uniq! # need to use uniq! here instead of checking for the item before adding
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def specs_by_name(name)
|
||||
@specs[name].values
|
||||
end
|
||||
|
||||
def search_by_dependency(dependency, base = nil)
|
||||
@cache[base || false] ||= {}
|
||||
@cache[base || false][dependency] ||= begin
|
||||
specs = specs_by_name(dependency.name)
|
||||
specs += base if base
|
||||
found = specs.select do |spec|
|
||||
next true if spec.source.is_a?(Source::Gemspec)
|
||||
if base # allow all platforms when searching from a lockfile
|
||||
dependency.matches_spec?(spec)
|
||||
else
|
||||
dependency.matches_spec?(spec) && Gem::Platform.match(spec.platform)
|
||||
end
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
end
|
||||
|
||||
EMPTY_SEARCH = [].freeze
|
||||
|
||||
def search_by_spec(spec)
|
||||
spec = @specs[spec.name][spec.full_name]
|
||||
spec ? [spec] : EMPTY_SEARCH
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,253 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Injector
|
||||
INJECTED_GEMS = "injected gems".freeze
|
||||
|
||||
def self.inject(new_deps, options = {})
|
||||
injector = new(new_deps, options)
|
||||
injector.inject(Bundler.default_gemfile, Bundler.default_lockfile)
|
||||
end
|
||||
|
||||
def self.remove(gems, options = {})
|
||||
injector = new(gems, options)
|
||||
injector.remove(Bundler.default_gemfile, Bundler.default_lockfile)
|
||||
end
|
||||
|
||||
def initialize(deps, options = {})
|
||||
@deps = deps
|
||||
@options = options
|
||||
end
|
||||
|
||||
# @param [Pathname] gemfile_path The Gemfile in which to inject the new dependency.
|
||||
# @param [Pathname] lockfile_path The lockfile in which to inject the new dependency.
|
||||
# @return [Array]
|
||||
def inject(gemfile_path, lockfile_path)
|
||||
if Bundler.frozen_bundle?
|
||||
# ensure the lock and Gemfile are synced
|
||||
Bundler.definition.ensure_equivalent_gemfile_and_lockfile(true)
|
||||
end
|
||||
|
||||
# temporarily unfreeze
|
||||
Bundler.settings.temporary(:deployment => false, :frozen => false) do
|
||||
# evaluate the Gemfile we have now
|
||||
builder = Dsl.new
|
||||
builder.eval_gemfile(gemfile_path)
|
||||
|
||||
# don't inject any gems that are already in the Gemfile
|
||||
@deps -= builder.dependencies
|
||||
|
||||
# add new deps to the end of the in-memory Gemfile
|
||||
# Set conservative versioning to false because
|
||||
# we want to let the resolver resolve the version first
|
||||
builder.eval_gemfile(INJECTED_GEMS, build_gem_lines(false)) if @deps.any?
|
||||
|
||||
# resolve to see if the new deps broke anything
|
||||
@definition = builder.to_definition(lockfile_path, {})
|
||||
@definition.resolve_remotely!
|
||||
|
||||
# since nothing broke, we can add those gems to the gemfile
|
||||
append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @deps.any?
|
||||
|
||||
# since we resolved successfully, write out the lockfile
|
||||
@definition.lock(Bundler.default_lockfile)
|
||||
|
||||
# invalidate the cached Bundler.definition
|
||||
Bundler.reset_paths!
|
||||
|
||||
# return an array of the deps that we added
|
||||
@deps
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
|
||||
# @param [Pathname] lockfile_path The lockfile from which to remove dependencies.
|
||||
# @return [Array]
|
||||
def remove(gemfile_path, lockfile_path)
|
||||
# remove gems from each gemfiles we have
|
||||
Bundler.definition.gemfiles.each do |path|
|
||||
deps = remove_deps(path)
|
||||
|
||||
show_warning("No gems were removed from the gemfile.") if deps.empty?
|
||||
|
||||
deps.each {|dep| Bundler.ui.confirm "#{SharedHelpers.pretty_dependency(dep, false)} was removed." }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conservative_version(spec)
|
||||
version = spec.version
|
||||
return ">= 0" if version.nil?
|
||||
segments = version.segments
|
||||
seg_end_index = version >= Gem::Version.new("1.0") ? 1 : 2
|
||||
|
||||
prerelease_suffix = version.to_s.gsub(version.release.to_s, "") if version.prerelease?
|
||||
"#{version_prefix}#{segments[0..seg_end_index].join(".")}#{prerelease_suffix}"
|
||||
end
|
||||
|
||||
def version_prefix
|
||||
if @options[:strict]
|
||||
"= "
|
||||
elsif @options[:optimistic]
|
||||
">= "
|
||||
else
|
||||
"~> "
|
||||
end
|
||||
end
|
||||
|
||||
def build_gem_lines(conservative_versioning)
|
||||
@deps.map do |d|
|
||||
name = d.name.dump
|
||||
|
||||
requirement = if conservative_versioning
|
||||
", \"#{conservative_version(@definition.specs[d.name][0])}\""
|
||||
else
|
||||
", #{d.requirement.as_list.map(&:dump).join(", ")}"
|
||||
end
|
||||
|
||||
if d.groups != Array(:default)
|
||||
group = d.groups.size == 1 ? ", :group => #{d.groups.first.inspect}" : ", :groups => #{d.groups.inspect}"
|
||||
end
|
||||
|
||||
source = ", :source => \"#{d.source}\"" unless d.source.nil?
|
||||
|
||||
%(gem #{name}#{requirement}#{group}#{source})
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def append_to(gemfile_path, new_gem_lines)
|
||||
gemfile_path.open("a") do |f|
|
||||
f.puts
|
||||
f.puts new_gem_lines
|
||||
end
|
||||
end
|
||||
|
||||
# evalutes a gemfile to remove the specified gem
|
||||
# from it.
|
||||
def remove_deps(gemfile_path)
|
||||
initial_gemfile = IO.readlines(gemfile_path)
|
||||
|
||||
Bundler.ui.info "Removing gems from #{gemfile_path}"
|
||||
|
||||
# evaluate the Gemfile we have
|
||||
builder = Dsl.new
|
||||
builder.eval_gemfile(gemfile_path)
|
||||
|
||||
removed_deps = remove_gems_from_dependencies(builder, @deps, gemfile_path)
|
||||
|
||||
# abort the opertion if no gems were removed
|
||||
# no need to operate on gemfile furthur
|
||||
return [] if removed_deps.empty?
|
||||
|
||||
cleaned_gemfile = remove_gems_from_gemfile(@deps, gemfile_path)
|
||||
|
||||
SharedHelpers.write_to_gemfile(gemfile_path, cleaned_gemfile)
|
||||
|
||||
# check for errors
|
||||
# including extra gems being removed
|
||||
# or some gems not being removed
|
||||
# and return the actual removed deps
|
||||
cross_check_for_errors(gemfile_path, builder.dependencies, removed_deps, initial_gemfile)
|
||||
end
|
||||
|
||||
# @param [Dsl] builder Dsl object of current Gemfile.
|
||||
# @param [Array] gems Array of names of gems to be removed.
|
||||
# @param [Pathname] path of the Gemfile
|
||||
# @return [Array] removed_deps Array of removed dependencies.
|
||||
def remove_gems_from_dependencies(builder, gems, gemfile_path)
|
||||
removed_deps = []
|
||||
|
||||
gems.each do |gem_name|
|
||||
deleted_dep = builder.dependencies.find {|d| d.name == gem_name }
|
||||
|
||||
if deleted_dep.nil?
|
||||
raise GemfileError, "`#{gem_name}` is not specified in #{gemfile_path} so it could not be removed."
|
||||
end
|
||||
|
||||
builder.dependencies.delete(deleted_dep)
|
||||
|
||||
removed_deps << deleted_dep
|
||||
end
|
||||
|
||||
removed_deps
|
||||
end
|
||||
|
||||
# @param [Array] gems Array of names of gems to be removed.
|
||||
# @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
|
||||
def remove_gems_from_gemfile(gems, gemfile_path)
|
||||
patterns = /gem\s+(['"])#{Regexp.union(gems)}\1|gem\s*\((['"])#{Regexp.union(gems)}\2\)/
|
||||
|
||||
# remove lines which match the regex
|
||||
new_gemfile = IO.readlines(gemfile_path).reject {|line| line.match(patterns) }
|
||||
|
||||
# remove lone \n and append them with other strings
|
||||
new_gemfile.each_with_index do |_line, index|
|
||||
if new_gemfile[index + 1] == "\n"
|
||||
new_gemfile[index] += new_gemfile[index + 1]
|
||||
new_gemfile.delete_at(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
%w[group source env install_if].each {|block| remove_nested_blocks(new_gemfile, block) }
|
||||
|
||||
new_gemfile.join.chomp
|
||||
end
|
||||
|
||||
# @param [Array] gemfile Array of gemfile contents.
|
||||
# @param [String] block_name Name of block name to look for.
|
||||
def remove_nested_blocks(gemfile, block_name)
|
||||
nested_blocks = 0
|
||||
|
||||
# count number of nested blocks
|
||||
gemfile.each_with_index {|line, index| nested_blocks += 1 if !gemfile[index + 1].nil? && gemfile[index + 1].include?(block_name) && line.include?(block_name) }
|
||||
|
||||
while nested_blocks >= 0
|
||||
nested_blocks -= 1
|
||||
|
||||
gemfile.each_with_index do |line, index|
|
||||
next unless !line.nil? && line.include?(block_name)
|
||||
if gemfile[index + 1] =~ /^\s*end\s*$/
|
||||
gemfile[index] = nil
|
||||
gemfile[index + 1] = nil
|
||||
end
|
||||
end
|
||||
|
||||
gemfile.compact!
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
|
||||
# @param [Array] original_deps Array of original dependencies.
|
||||
# @param [Array] removed_deps Array of removed dependencies.
|
||||
# @param [Array] initial_gemfile Contents of original Gemfile before any operation.
|
||||
def cross_check_for_errors(gemfile_path, original_deps, removed_deps, initial_gemfile)
|
||||
# evalute the new gemfile to look for any failure cases
|
||||
builder = Dsl.new
|
||||
builder.eval_gemfile(gemfile_path)
|
||||
|
||||
# record gems which were removed but not requested
|
||||
extra_removed_gems = original_deps - builder.dependencies
|
||||
|
||||
# if some extra gems were removed then raise error
|
||||
# and revert Gemfile to original
|
||||
unless extra_removed_gems.empty?
|
||||
SharedHelpers.write_to_gemfile(gemfile_path, initial_gemfile.join)
|
||||
|
||||
raise InvalidOption, "Gems could not be removed. #{extra_removed_gems.join(", ")} would also have been removed. Bundler cannot continue."
|
||||
end
|
||||
|
||||
# record gems which could not be removed due to some reasons
|
||||
errored_deps = builder.dependencies.select {|d| d.gemfile == gemfile_path } & removed_deps.select {|d| d.gemfile == gemfile_path }
|
||||
|
||||
show_warning "#{errored_deps.map(&:name).join(", ")} could not be removed." unless errored_deps.empty?
|
||||
|
||||
# return actual removed dependencies
|
||||
removed_deps - errored_deps
|
||||
end
|
||||
|
||||
def show_warning(message)
|
||||
Bundler.ui.info Bundler.ui.add_color(message, :yellow)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/compatibility_guard"
|
||||
|
||||
# Allows for declaring a Gemfile inline in a ruby script, optionally installing
|
||||
# any gems that aren't already installed on the user's system.
|
||||
#
|
||||
# @note Every gem that is specified in this 'Gemfile' will be `require`d, as if
|
||||
# the user had manually called `Bundler.require`. To avoid a requested gem
|
||||
# being automatically required, add the `:require => false` option to the
|
||||
# `gem` dependency declaration.
|
||||
#
|
||||
# @param install [Boolean] whether gems that aren't already installed on the
|
||||
# user's system should be installed.
|
||||
# Defaults to `false`.
|
||||
#
|
||||
# @param gemfile [Proc] a block that is evaluated as a `Gemfile`.
|
||||
#
|
||||
# @example Using an inline Gemfile
|
||||
#
|
||||
# #!/usr/bin/env ruby
|
||||
#
|
||||
# require 'bundler/inline'
|
||||
#
|
||||
# gemfile do
|
||||
# source 'https://rubygems.org'
|
||||
# gem 'json', require: false
|
||||
# gem 'nap', require: 'rest'
|
||||
# gem 'cocoapods', '~> 0.34.1'
|
||||
# end
|
||||
#
|
||||
# puts Pod::VERSION # => "0.34.4"
|
||||
#
|
||||
def gemfile(install = false, options = {}, &gemfile)
|
||||
require "bundler"
|
||||
|
||||
opts = options.dup
|
||||
ui = opts.delete(:ui) { Bundler::UI::Shell.new }
|
||||
raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty?
|
||||
|
||||
old_root = Bundler.method(:root)
|
||||
def Bundler.root
|
||||
Bundler::SharedHelpers.pwd.expand_path
|
||||
end
|
||||
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile"
|
||||
|
||||
Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins?
|
||||
builder = Bundler::Dsl.new
|
||||
builder.instance_eval(&gemfile)
|
||||
|
||||
definition = builder.to_definition(nil, true)
|
||||
def definition.lock(*); end
|
||||
definition.validate_runtime!
|
||||
|
||||
missing_specs = proc do
|
||||
definition.missing_specs?
|
||||
end
|
||||
|
||||
Bundler.ui = ui if install
|
||||
if install || missing_specs.call
|
||||
Bundler.settings.temporary(:inline => true) do
|
||||
installer = Bundler::Installer.install(Bundler.root, definition, :system => true)
|
||||
installer.post_install_messages.each do |name, message|
|
||||
Bundler.ui.info "Post-install message from #{name}:\n#{message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
runtime = Bundler::Runtime.new(nil, definition)
|
||||
runtime.setup.require
|
||||
ensure
|
||||
bundler_module = class << Bundler; self; end
|
||||
bundler_module.send(:define_method, :root, old_root) if old_root
|
||||
end
|
|
@ -0,0 +1,318 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "erb"
|
||||
require "rubygems/dependency_installer"
|
||||
require "bundler/worker"
|
||||
require "bundler/installer/parallel_installer"
|
||||
require "bundler/installer/standalone"
|
||||
require "bundler/installer/gem_installer"
|
||||
|
||||
module Bundler
|
||||
class Installer
|
||||
class << self
|
||||
attr_accessor :ambiguous_gems
|
||||
|
||||
Installer.ambiguous_gems = []
|
||||
end
|
||||
|
||||
attr_reader :post_install_messages
|
||||
|
||||
# Begins the installation process for Bundler.
|
||||
# For more information see the #run method on this class.
|
||||
def self.install(root, definition, options = {})
|
||||
installer = new(root, definition)
|
||||
Plugin.hook(Plugin::Events::GEM_BEFORE_INSTALL_ALL, definition.dependencies)
|
||||
installer.run(options)
|
||||
Plugin.hook(Plugin::Events::GEM_AFTER_INSTALL_ALL, definition.dependencies)
|
||||
installer
|
||||
end
|
||||
|
||||
def initialize(root, definition)
|
||||
@root = root
|
||||
@definition = definition
|
||||
@post_install_messages = {}
|
||||
end
|
||||
|
||||
# Runs the install procedures for a specific Gemfile.
|
||||
#
|
||||
# Firstly, this method will check to see if `Bundler.bundle_path` exists
|
||||
# and if not then Bundler will create the directory. This is usually the same
|
||||
# location as RubyGems which typically is the `~/.gem` directory
|
||||
# unless other specified.
|
||||
#
|
||||
# Secondly, it checks if Bundler has been configured to be "frozen".
|
||||
# Frozen ensures that the Gemfile and the Gemfile.lock file are matching.
|
||||
# This stops a situation where a developer may update the Gemfile but may not run
|
||||
# `bundle install`, which leads to the Gemfile.lock file not being correctly updated.
|
||||
# If this file is not correctly updated then any other developer running
|
||||
# `bundle install` will potentially not install the correct gems.
|
||||
#
|
||||
# Thirdly, Bundler checks if there are any dependencies specified in the Gemfile.
|
||||
# If there are no dependencies specified then Bundler returns a warning message stating
|
||||
# so and this method returns.
|
||||
#
|
||||
# Fourthly, Bundler checks if the Gemfile.lock exists, and if so
|
||||
# then proceeds to set up a definition based on the Gemfile and the Gemfile.lock.
|
||||
# During this step Bundler will also download information about any new gems
|
||||
# that are not in the Gemfile.lock and resolve any dependencies if needed.
|
||||
#
|
||||
# Fifthly, Bundler resolves the dependencies either through a cache of gems or by remote.
|
||||
# This then leads into the gems being installed, along with stubs for their executables,
|
||||
# but only if the --binstubs option has been passed or Bundler.options[:bin] has been set
|
||||
# earlier.
|
||||
#
|
||||
# Sixthly, a new Gemfile.lock is created from the installed gems to ensure that the next time
|
||||
# that a user runs `bundle install` they will receive any updates from this process.
|
||||
#
|
||||
# Finally, if the user has specified the standalone flag, Bundler will generate the needed
|
||||
# require paths and save them in a `setup.rb` file. See `bundle standalone --help` for more
|
||||
# information.
|
||||
def run(options)
|
||||
create_bundle_path
|
||||
|
||||
ProcessLock.lock do
|
||||
if Bundler.frozen_bundle?
|
||||
@definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])
|
||||
end
|
||||
|
||||
if @definition.dependencies.empty?
|
||||
Bundler.ui.warn "The Gemfile specifies no dependencies"
|
||||
lock
|
||||
return
|
||||
end
|
||||
|
||||
if resolve_if_needed(options)
|
||||
ensure_specs_are_compatible!
|
||||
warn_on_incompatible_bundler_deps
|
||||
load_plugins
|
||||
options.delete(:jobs)
|
||||
else
|
||||
options[:jobs] = 1 # to avoid the overhead of Bundler::Worker
|
||||
end
|
||||
install(options)
|
||||
|
||||
lock unless Bundler.frozen_bundle?
|
||||
Standalone.new(options[:standalone], @definition).generate if options[:standalone]
|
||||
end
|
||||
end
|
||||
|
||||
def generate_bundler_executable_stubs(spec, options = {})
|
||||
if options[:binstubs_cmd] && spec.executables.empty?
|
||||
options = {}
|
||||
spec.runtime_dependencies.each do |dep|
|
||||
bins = @definition.specs[dep].first.executables
|
||||
options[dep.name] = bins unless bins.empty?
|
||||
end
|
||||
if options.any?
|
||||
Bundler.ui.warn "#{spec.name} has no executables, but you may want " \
|
||||
"one from a gem it depends on."
|
||||
options.each {|name, bins| Bundler.ui.warn " #{name} has: #{bins.join(", ")}" }
|
||||
else
|
||||
Bundler.ui.warn "There are no executables for the gem #{spec.name}."
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# double-assignment to avoid warnings about variables that will be used by ERB
|
||||
bin_path = Bundler.bin_path
|
||||
bin_path = bin_path
|
||||
relative_gemfile_path = Bundler.default_gemfile.relative_path_from(bin_path)
|
||||
relative_gemfile_path = relative_gemfile_path
|
||||
ruby_command = Thor::Util.ruby_command
|
||||
ruby_command = ruby_command
|
||||
template_path = File.expand_path("../templates/Executable", __FILE__)
|
||||
if spec.name == "bundler"
|
||||
template_path += ".bundler"
|
||||
spec.executables = %(bundle)
|
||||
end
|
||||
template = File.read(template_path)
|
||||
|
||||
exists = []
|
||||
spec.executables.each do |executable|
|
||||
binstub_path = "#{bin_path}/#{executable}"
|
||||
if File.exist?(binstub_path) && !options[:force]
|
||||
exists << executable
|
||||
next
|
||||
end
|
||||
|
||||
File.open(binstub_path, "w", 0o777 & ~File.umask) do |f|
|
||||
if RUBY_VERSION >= "2.6"
|
||||
f.puts ERB.new(template, :trim_mode => "-").result(binding)
|
||||
else
|
||||
f.puts ERB.new(template, nil, "-").result(binding)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if options[:binstubs_cmd] && exists.any?
|
||||
case exists.size
|
||||
when 1
|
||||
Bundler.ui.warn "Skipped #{exists[0]} since it already exists."
|
||||
when 2
|
||||
Bundler.ui.warn "Skipped #{exists.join(" and ")} since they already exist."
|
||||
else
|
||||
items = exists[0...-1].empty? ? nil : exists[0...-1].join(", ")
|
||||
skipped = [items, exists[-1]].compact.join(" and ")
|
||||
Bundler.ui.warn "Skipped #{skipped} since they already exist."
|
||||
end
|
||||
Bundler.ui.warn "If you want to overwrite skipped stubs, use --force."
|
||||
end
|
||||
end
|
||||
|
||||
def generate_standalone_bundler_executable_stubs(spec)
|
||||
# double-assignment to avoid warnings about variables that will be used by ERB
|
||||
bin_path = Bundler.bin_path
|
||||
unless path = Bundler.settings[:path]
|
||||
raise "Can't standalone without an explicit path set"
|
||||
end
|
||||
standalone_path = Bundler.root.join(path).relative_path_from(bin_path)
|
||||
standalone_path = standalone_path
|
||||
template = File.read(File.expand_path("../templates/Executable.standalone", __FILE__))
|
||||
ruby_command = Thor::Util.ruby_command
|
||||
ruby_command = ruby_command
|
||||
|
||||
spec.executables.each do |executable|
|
||||
next if executable == "bundle"
|
||||
executable_path = Pathname(spec.full_gem_path).join(spec.bindir, executable).relative_path_from(bin_path)
|
||||
executable_path = executable_path
|
||||
File.open "#{bin_path}/#{executable}", "w", 0o755 do |f|
|
||||
if RUBY_VERSION >= "2.6"
|
||||
f.puts ERB.new(template, :trim_mode => "-").result(binding)
|
||||
else
|
||||
f.puts ERB.new(template, nil, "-").result(binding)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# the order that the resolver provides is significant, since
|
||||
# dependencies might affect the installation of a gem.
|
||||
# that said, it's a rare situation (other than rake), and parallel
|
||||
# installation is SO MUCH FASTER. so we let people opt in.
|
||||
def install(options)
|
||||
force = options["force"]
|
||||
jobs = installation_parallelization(options)
|
||||
install_in_parallel jobs, options[:standalone], force
|
||||
end
|
||||
|
||||
def installation_parallelization(options)
|
||||
if jobs = options.delete(:jobs)
|
||||
return jobs
|
||||
end
|
||||
|
||||
return 1 unless can_install_in_parallel?
|
||||
|
||||
auto_config_jobs = Bundler.feature_flag.auto_config_jobs?
|
||||
if jobs = Bundler.settings[:jobs]
|
||||
if auto_config_jobs
|
||||
jobs
|
||||
else
|
||||
[jobs.pred, 1].max
|
||||
end
|
||||
elsif auto_config_jobs
|
||||
processor_count
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
def processor_count
|
||||
require "etc"
|
||||
Etc.nprocessors
|
||||
rescue
|
||||
1
|
||||
end
|
||||
|
||||
def load_plugins
|
||||
Bundler.rubygems.load_plugins
|
||||
|
||||
requested_path_gems = @definition.requested_specs.select {|s| s.source.is_a?(Source::Path) }
|
||||
path_plugin_files = requested_path_gems.map do |spec|
|
||||
begin
|
||||
Bundler.rubygems.spec_matches_for_glob(spec, "rubygems_plugin#{Bundler.rubygems.suffix_pattern}")
|
||||
rescue TypeError
|
||||
error_message = "#{spec.name} #{spec.version} has an invalid gemspec"
|
||||
raise Gem::InvalidSpecificationException, error_message
|
||||
end
|
||||
end.flatten
|
||||
Bundler.rubygems.load_plugin_files(path_plugin_files)
|
||||
end
|
||||
|
||||
def ensure_specs_are_compatible!
|
||||
system_ruby = Bundler::RubyVersion.system
|
||||
rubygems_version = Gem::Version.create(Gem::VERSION)
|
||||
@definition.specs.each do |spec|
|
||||
if required_ruby_version = spec.required_ruby_version
|
||||
unless required_ruby_version.satisfied_by?(system_ruby.gem_version)
|
||||
raise InstallError, "#{spec.full_name} requires ruby version #{required_ruby_version}, " \
|
||||
"which is incompatible with the current version, #{system_ruby}"
|
||||
end
|
||||
end
|
||||
next unless required_rubygems_version = spec.required_rubygems_version
|
||||
unless required_rubygems_version.satisfied_by?(rubygems_version)
|
||||
raise InstallError, "#{spec.full_name} requires rubygems version #{required_rubygems_version}, " \
|
||||
"which is incompatible with the current version, #{rubygems_version}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def warn_on_incompatible_bundler_deps
|
||||
bundler_version = Gem::Version.create(Bundler::VERSION)
|
||||
@definition.specs.each do |spec|
|
||||
spec.dependencies.each do |dep|
|
||||
next if dep.type == :development
|
||||
next unless dep.name == "bundler".freeze
|
||||
next if dep.requirement.satisfied_by?(bundler_version)
|
||||
|
||||
Bundler.ui.warn "#{spec.name} (#{spec.version}) has dependency" \
|
||||
" #{SharedHelpers.pretty_dependency(dep)}" \
|
||||
", which is unsatisfied by the current bundler version #{VERSION}" \
|
||||
", so the dependency is being ignored"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def can_install_in_parallel?
|
||||
if Bundler.rubygems.provides?(">= 2.1.0")
|
||||
true
|
||||
else
|
||||
Bundler.ui.warn "RubyGems #{Gem::VERSION} is not threadsafe, so your "\
|
||||
"gems will be installed one at a time. Upgrade to RubyGems 2.1.0 " \
|
||||
"or higher to enable parallel gem installation."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def install_in_parallel(size, standalone, force = false)
|
||||
spec_installations = ParallelInstaller.call(self, @definition.specs, size, standalone, force)
|
||||
spec_installations.each do |installation|
|
||||
post_install_messages[installation.name] = installation.post_install_message if installation.has_post_install_message?
|
||||
end
|
||||
end
|
||||
|
||||
def create_bundle_path
|
||||
SharedHelpers.filesystem_access(Bundler.bundle_path.to_s) do |p|
|
||||
Bundler.mkdir_p(p)
|
||||
end unless Bundler.bundle_path.exist?
|
||||
rescue Errno::EEXIST
|
||||
raise PathError, "Could not install to path `#{Bundler.bundle_path}` " \
|
||||
"because a file already exists at that path. Either remove or rename the file so the directory can be created."
|
||||
end
|
||||
|
||||
# returns whether or not a re-resolve was needed
|
||||
def resolve_if_needed(options)
|
||||
if !@definition.unlocking? && !options["force"] && !Bundler.settings[:inline] && Bundler.default_lockfile.file?
|
||||
return false if @definition.nothing_changed? && !@definition.missing_specs?
|
||||
end
|
||||
|
||||
options["local"] ? @definition.resolve_with_cache! : @definition.resolve_remotely!
|
||||
true
|
||||
end
|
||||
|
||||
def lock(opts = {})
|
||||
@definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,85 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class GemInstaller
|
||||
attr_reader :spec, :standalone, :worker, :force, :installer
|
||||
|
||||
def initialize(spec, installer, standalone = false, worker = 0, force = false)
|
||||
@spec = spec
|
||||
@installer = installer
|
||||
@standalone = standalone
|
||||
@worker = worker
|
||||
@force = force
|
||||
end
|
||||
|
||||
def install_from_spec
|
||||
post_install_message = spec_settings ? install_with_settings : install
|
||||
Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
|
||||
generate_executable_stubs
|
||||
return true, post_install_message
|
||||
rescue Bundler::InstallHookError, Bundler::SecurityError, APIResponseMismatchError
|
||||
raise
|
||||
rescue Errno::ENOSPC
|
||||
return false, out_of_space_message
|
||||
rescue StandardError => e
|
||||
return false, specific_failure_message(e)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def specific_failure_message(e)
|
||||
message = "#{e.class}: #{e.message}\n"
|
||||
message += " " + e.backtrace.join("\n ") + "\n\n" if Bundler.ui.debug?
|
||||
message = message.lines.first + Bundler.ui.add_color(message.lines.drop(1).join, :clear)
|
||||
message + Bundler.ui.add_color(failure_message, :red)
|
||||
end
|
||||
|
||||
def failure_message
|
||||
return install_error_message if spec.source.options["git"]
|
||||
"#{install_error_message}\n#{gem_install_message}"
|
||||
end
|
||||
|
||||
def install_error_message
|
||||
"An error occurred while installing #{spec.name} (#{spec.version}), and Bundler cannot continue."
|
||||
end
|
||||
|
||||
def gem_install_message
|
||||
source = spec.source
|
||||
return unless source.respond_to?(:remotes)
|
||||
|
||||
if source.remotes.size == 1
|
||||
"Make sure that `gem install #{spec.name} -v '#{spec.version}' --source '#{source.remotes.first}'` succeeds before bundling."
|
||||
else
|
||||
"Make sure that `gem install #{spec.name} -v '#{spec.version}'` succeeds before bundling."
|
||||
end
|
||||
end
|
||||
|
||||
def spec_settings
|
||||
# Fetch the build settings, if there are any
|
||||
Bundler.settings["build.#{spec.name}"]
|
||||
end
|
||||
|
||||
def install
|
||||
spec.source.install(spec, :force => force, :ensure_builtin_gems_cached => standalone, :build_args => Array(spec_settings))
|
||||
end
|
||||
|
||||
def install_with_settings
|
||||
# Build arguments are global, so this is mutexed
|
||||
Bundler.rubygems.install_with_build_args([spec_settings]) { install }
|
||||
end
|
||||
|
||||
def out_of_space_message
|
||||
"#{install_error_message}\nYour disk is out of space. Free some space to be able to install your bundle."
|
||||
end
|
||||
|
||||
def generate_executable_stubs
|
||||
return if Bundler.feature_flag.forget_cli_options?
|
||||
return if Bundler.settings[:inline]
|
||||
if Bundler.settings[:bin] && standalone
|
||||
installer.generate_standalone_bundler_executable_stubs(spec)
|
||||
elsif Bundler.settings[:bin]
|
||||
installer.generate_bundler_executable_stubs(spec, :force => true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,233 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/worker"
|
||||
require "bundler/installer/gem_installer"
|
||||
|
||||
module Bundler
|
||||
class ParallelInstaller
|
||||
class SpecInstallation
|
||||
attr_accessor :spec, :name, :post_install_message, :state, :error
|
||||
def initialize(spec)
|
||||
@spec = spec
|
||||
@name = spec.name
|
||||
@state = :none
|
||||
@post_install_message = ""
|
||||
@error = nil
|
||||
end
|
||||
|
||||
def installed?
|
||||
state == :installed
|
||||
end
|
||||
|
||||
def enqueued?
|
||||
state == :enqueued
|
||||
end
|
||||
|
||||
def failed?
|
||||
state == :failed
|
||||
end
|
||||
|
||||
def installation_attempted?
|
||||
installed? || failed?
|
||||
end
|
||||
|
||||
# Only true when spec in neither installed nor already enqueued
|
||||
def ready_to_enqueue?
|
||||
!enqueued? && !installation_attempted?
|
||||
end
|
||||
|
||||
def has_post_install_message?
|
||||
!post_install_message.empty?
|
||||
end
|
||||
|
||||
def ignorable_dependency?(dep)
|
||||
dep.type == :development || dep.name == @name
|
||||
end
|
||||
|
||||
# Checks installed dependencies against spec's dependencies to make
|
||||
# sure needed dependencies have been installed.
|
||||
def dependencies_installed?(all_specs)
|
||||
installed_specs = all_specs.select(&:installed?).map(&:name)
|
||||
dependencies.all? {|d| installed_specs.include? d.name }
|
||||
end
|
||||
|
||||
# Represents only the non-development dependencies, the ones that are
|
||||
# itself and are in the total list.
|
||||
def dependencies
|
||||
@dependencies ||= begin
|
||||
all_dependencies.reject {|dep| ignorable_dependency? dep }
|
||||
end
|
||||
end
|
||||
|
||||
def missing_lockfile_dependencies(all_spec_names)
|
||||
deps = all_dependencies.reject {|dep| ignorable_dependency? dep }
|
||||
deps.reject {|dep| all_spec_names.include? dep.name }
|
||||
end
|
||||
|
||||
# Represents all dependencies
|
||||
def all_dependencies
|
||||
@spec.dependencies
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#<#{self.class} #{@spec.full_name} (#{state})>"
|
||||
end
|
||||
end
|
||||
|
||||
def self.call(*args)
|
||||
new(*args).call
|
||||
end
|
||||
|
||||
attr_reader :size
|
||||
|
||||
def initialize(installer, all_specs, size, standalone, force)
|
||||
@installer = installer
|
||||
@size = size
|
||||
@standalone = standalone
|
||||
@force = force
|
||||
@specs = all_specs.map {|s| SpecInstallation.new(s) }
|
||||
@spec_set = all_specs
|
||||
@rake = @specs.find {|s| s.name == "rake" }
|
||||
end
|
||||
|
||||
def call
|
||||
# Since `autoload` has the potential for threading issues on 1.8.7
|
||||
# TODO: remove in bundler 2.0
|
||||
require "bundler/gem_remote_fetcher" if RUBY_VERSION < "1.9"
|
||||
|
||||
check_for_corrupt_lockfile
|
||||
|
||||
if @size > 1
|
||||
install_with_worker
|
||||
else
|
||||
install_serially
|
||||
end
|
||||
|
||||
handle_error if @specs.any?(&:failed?)
|
||||
@specs
|
||||
ensure
|
||||
worker_pool && worker_pool.stop
|
||||
end
|
||||
|
||||
def check_for_corrupt_lockfile
|
||||
missing_dependencies = @specs.map do |s|
|
||||
[
|
||||
s,
|
||||
s.missing_lockfile_dependencies(@specs.map(&:name)),
|
||||
]
|
||||
end.reject { |a| a.last.empty? }
|
||||
return if missing_dependencies.empty?
|
||||
|
||||
warning = []
|
||||
warning << "Your lockfile was created by an old Bundler that left some things out."
|
||||
if @size != 1
|
||||
warning << "Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing #{@size} at a time."
|
||||
@size = 1
|
||||
end
|
||||
warning << "You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile."
|
||||
warning << "The missing gems are:"
|
||||
|
||||
missing_dependencies.each do |spec, missing|
|
||||
warning << "* #{missing.map(&:name).join(", ")} depended upon by #{spec.name}"
|
||||
end
|
||||
|
||||
Bundler.ui.warn(warning.join("\n"))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def install_with_worker
|
||||
enqueue_specs
|
||||
process_specs until finished_installing?
|
||||
end
|
||||
|
||||
def install_serially
|
||||
until finished_installing?
|
||||
raise "failed to find a spec to enqueue while installing serially" unless spec_install = @specs.find(&:ready_to_enqueue?)
|
||||
spec_install.state = :enqueued
|
||||
do_install(spec_install, 0)
|
||||
end
|
||||
end
|
||||
|
||||
def worker_pool
|
||||
@worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda { |spec_install, worker_num|
|
||||
do_install(spec_install, worker_num)
|
||||
}
|
||||
end
|
||||
|
||||
def do_install(spec_install, worker_num)
|
||||
Plugin.hook(Plugin::Events::GEM_BEFORE_INSTALL, spec_install)
|
||||
gem_installer = Bundler::GemInstaller.new(
|
||||
spec_install.spec, @installer, @standalone, worker_num, @force
|
||||
)
|
||||
success, message = begin
|
||||
gem_installer.install_from_spec
|
||||
rescue RuntimeError => e
|
||||
raise e, "#{e}\n\n#{require_tree_for_spec(spec_install.spec)}"
|
||||
end
|
||||
if success
|
||||
spec_install.state = :installed
|
||||
spec_install.post_install_message = message unless message.nil?
|
||||
else
|
||||
spec_install.state = :failed
|
||||
spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
|
||||
end
|
||||
Plugin.hook(Plugin::Events::GEM_AFTER_INSTALL, spec_install)
|
||||
spec_install
|
||||
end
|
||||
|
||||
# Dequeue a spec and save its post-install message and then enqueue the
|
||||
# remaining specs.
|
||||
# Some specs might've had to wait til this spec was installed to be
|
||||
# processed so the call to `enqueue_specs` is important after every
|
||||
# dequeue.
|
||||
def process_specs
|
||||
worker_pool.deq
|
||||
enqueue_specs
|
||||
end
|
||||
|
||||
def finished_installing?
|
||||
@specs.all? do |spec|
|
||||
return true if spec.failed?
|
||||
spec.installed?
|
||||
end
|
||||
end
|
||||
|
||||
def handle_error
|
||||
errors = @specs.select(&:failed?).map(&:error)
|
||||
if exception = errors.find {|e| e.is_a?(Bundler::BundlerError) }
|
||||
raise exception
|
||||
end
|
||||
raise Bundler::InstallError, errors.map(&:to_s).join("\n\n")
|
||||
end
|
||||
|
||||
def require_tree_for_spec(spec)
|
||||
tree = @spec_set.what_required(spec)
|
||||
t = String.new("In #{File.basename(SharedHelpers.default_gemfile)}:\n")
|
||||
tree.each_with_index do |s, depth|
|
||||
t << " " * depth.succ << s.name
|
||||
unless tree.last == s
|
||||
t << %( was resolved to #{s.version}, which depends on)
|
||||
end
|
||||
t << %(\n)
|
||||
end
|
||||
t
|
||||
end
|
||||
|
||||
# Keys in the remains hash represent uninstalled gems specs.
|
||||
# We enqueue all gem specs that do not have any dependencies.
|
||||
# Later we call this lambda again to install specs that depended on
|
||||
# previously installed specifications. We continue until all specs
|
||||
# are installed.
|
||||
def enqueue_specs
|
||||
@specs.select(&:ready_to_enqueue?).each do |spec|
|
||||
next if @rake && !@rake.installed? && spec.name != @rake.name
|
||||
|
||||
if spec.dependencies_installed? @specs
|
||||
spec.state = :enqueued
|
||||
worker_pool.enq spec
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Standalone
|
||||
def initialize(groups, definition)
|
||||
@specs = groups.empty? ? definition.requested_specs : definition.specs_for(groups.map(&:to_sym))
|
||||
end
|
||||
|
||||
def generate
|
||||
SharedHelpers.filesystem_access(bundler_path) do |p|
|
||||
FileUtils.mkdir_p(p)
|
||||
end
|
||||
File.open File.join(bundler_path, "setup.rb"), "w" do |file|
|
||||
file.puts "require 'rbconfig'"
|
||||
file.puts "# ruby 1.8.7 doesn't define RUBY_ENGINE"
|
||||
file.puts "ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'"
|
||||
file.puts "ruby_version = RbConfig::CONFIG[\"ruby_version\"]"
|
||||
file.puts "path = File.expand_path('..', __FILE__)"
|
||||
paths.each do |path|
|
||||
file.puts %($:.unshift "\#{path}/#{path}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def paths
|
||||
@specs.map do |spec|
|
||||
next if spec.name == "bundler"
|
||||
Array(spec.require_paths).map do |path|
|
||||
gem_path(path, spec).sub(version_dir, '#{ruby_engine}/#{ruby_version}')
|
||||
# This is a static string intentionally. It's interpolated at a later time.
|
||||
end
|
||||
end.flatten
|
||||
end
|
||||
|
||||
def version_dir
|
||||
"#{Bundler::RubyVersion.system.engine}/#{RbConfig::CONFIG["ruby_version"]}"
|
||||
end
|
||||
|
||||
def bundler_path
|
||||
Bundler.root.join(Bundler.settings[:path], "bundler")
|
||||
end
|
||||
|
||||
def gem_path(path, spec)
|
||||
full_path = Pathname.new(path).absolute? ? path : File.join(spec.full_gem_path, path)
|
||||
Pathname.new(full_path).relative_path_from(Bundler.root.join(bundler_path)).to_s
|
||||
rescue TypeError
|
||||
error_message = "#{spec.name} #{spec.version} has an invalid gemspec"
|
||||
raise Gem::InvalidSpecificationException.new(error_message)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,123 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "uri"
|
||||
require "bundler/match_platform"
|
||||
|
||||
module Bundler
|
||||
class LazySpecification
|
||||
Identifier = Struct.new(:name, :version, :source, :platform, :dependencies)
|
||||
class Identifier
|
||||
include Comparable
|
||||
def <=>(other)
|
||||
return unless other.is_a?(Identifier)
|
||||
[name, version, platform_string] <=> [other.name, other.version, other.platform_string]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def platform_string
|
||||
platform_string = platform.to_s
|
||||
platform_string == Index::RUBY ? Index::NULL : platform_string
|
||||
end
|
||||
end
|
||||
|
||||
include MatchPlatform
|
||||
|
||||
attr_reader :name, :version, :dependencies, :platform
|
||||
attr_accessor :source, :remote
|
||||
|
||||
def initialize(name, version, platform, source = nil)
|
||||
@name = name
|
||||
@version = version
|
||||
@dependencies = []
|
||||
@platform = platform || Gem::Platform::RUBY
|
||||
@source = source
|
||||
@specification = nil
|
||||
end
|
||||
|
||||
def full_name
|
||||
if platform == Gem::Platform::RUBY || platform.nil?
|
||||
"#{@name}-#{@version}"
|
||||
else
|
||||
"#{@name}-#{@version}-#{platform}"
|
||||
end
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
identifier == other.identifier
|
||||
end
|
||||
|
||||
def satisfies?(dependency)
|
||||
@name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version))
|
||||
end
|
||||
|
||||
def to_lock
|
||||
out = String.new
|
||||
|
||||
if platform == Gem::Platform::RUBY || platform.nil?
|
||||
out << " #{name} (#{version})\n"
|
||||
else
|
||||
out << " #{name} (#{version}-#{platform})\n"
|
||||
end
|
||||
|
||||
dependencies.sort_by(&:to_s).uniq.each do |dep|
|
||||
next if dep.type == :development
|
||||
out << " #{dep.to_lock}\n"
|
||||
end
|
||||
|
||||
out
|
||||
end
|
||||
|
||||
def __materialize__
|
||||
search_object = Bundler.feature_flag.specific_platform? || Bundler.settings[:force_ruby_platform] ? self : Dependency.new(name, version)
|
||||
@specification = if source.is_a?(Source::Gemspec) && source.gemspec.name == name
|
||||
source.gemspec.tap {|s| s.source = source }
|
||||
else
|
||||
search = source.specs.search(search_object).last
|
||||
if search && Gem::Platform.new(search.platform) != Gem::Platform.new(platform) && !search.runtime_dependencies.-(dependencies.reject {|d| d.type == :development }).empty?
|
||||
Bundler.ui.warn "Unable to use the platform-specific (#{search.platform}) version of #{name} (#{version}) " \
|
||||
"because it has different dependencies from the #{platform} version. " \
|
||||
"To use the platform-specific version of the gem, run `bundle config specific_platform true` and install again."
|
||||
search = source.specs.search(self).last
|
||||
end
|
||||
search.dependencies = dependencies if search && (search.is_a?(RemoteSpecification) || search.is_a?(EndpointSpecification))
|
||||
search
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to?(*args)
|
||||
super || @specification ? @specification.respond_to?(*args) : nil
|
||||
end
|
||||
|
||||
def to_s
|
||||
@__to_s ||= if platform == Gem::Platform::RUBY || platform.nil?
|
||||
"#{name} (#{version})"
|
||||
else
|
||||
"#{name} (#{version}-#{platform})"
|
||||
end
|
||||
end
|
||||
|
||||
def identifier
|
||||
@__identifier ||= Identifier.new(name, version, source, platform, dependencies)
|
||||
end
|
||||
|
||||
def git_version
|
||||
return unless source.is_a?(Bundler::Source::Git)
|
||||
" #{source.revision[0..6]}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_ary
|
||||
nil
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &blk)
|
||||
raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification
|
||||
|
||||
return super unless respond_to?(method)
|
||||
|
||||
@specification.send(method, *args, &blk)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,95 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class LockfileGenerator
|
||||
attr_reader :definition
|
||||
attr_reader :out
|
||||
|
||||
# @private
|
||||
def initialize(definition)
|
||||
@definition = definition
|
||||
@out = String.new
|
||||
end
|
||||
|
||||
def self.generate(definition)
|
||||
new(definition).generate!
|
||||
end
|
||||
|
||||
def generate!
|
||||
add_sources
|
||||
add_platforms
|
||||
add_dependencies
|
||||
add_locked_ruby_version
|
||||
add_bundled_with
|
||||
|
||||
out
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_sources
|
||||
definition.send(:sources).lock_sources.each_with_index do |source, idx|
|
||||
out << "\n" unless idx.zero?
|
||||
|
||||
# Add the source header
|
||||
out << source.to_lock
|
||||
|
||||
# Find all specs for this source
|
||||
specs = definition.resolve.select {|s| source.can_lock?(s) }
|
||||
add_specs(specs)
|
||||
end
|
||||
end
|
||||
|
||||
def add_specs(specs)
|
||||
# This needs to be sorted by full name so that
|
||||
# gems with the same name, but different platform
|
||||
# are ordered consistently
|
||||
specs.sort_by(&:full_name).each do |spec|
|
||||
next if spec.name == "bundler".freeze
|
||||
out << spec.to_lock
|
||||
end
|
||||
end
|
||||
|
||||
def add_platforms
|
||||
add_section("PLATFORMS", definition.platforms)
|
||||
end
|
||||
|
||||
def add_dependencies
|
||||
out << "\nDEPENDENCIES\n"
|
||||
|
||||
handled = []
|
||||
definition.dependencies.sort_by(&:to_s).each do |dep|
|
||||
next if handled.include?(dep.name)
|
||||
out << dep.to_lock
|
||||
handled << dep.name
|
||||
end
|
||||
end
|
||||
|
||||
def add_locked_ruby_version
|
||||
return unless locked_ruby_version = definition.locked_ruby_version
|
||||
add_section("RUBY VERSION", locked_ruby_version.to_s)
|
||||
end
|
||||
|
||||
def add_bundled_with
|
||||
add_section("BUNDLED WITH", definition.locked_bundler_version.to_s)
|
||||
end
|
||||
|
||||
def add_section(name, value)
|
||||
out << "\n#{name}\n"
|
||||
case value
|
||||
when Array
|
||||
value.map(&:to_s).sort.each do |val|
|
||||
out << " #{val}\n"
|
||||
end
|
||||
when Hash
|
||||
value.to_a.sort_by {|k, _| k.to_s }.each do |key, val|
|
||||
out << " #{key}: #{val}\n"
|
||||
end
|
||||
when String
|
||||
out << " #{value}\n"
|
||||
else
|
||||
raise ArgumentError, "#{value.inspect} can't be serialized in a lockfile"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,256 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Some versions of the Bundler 1.1 RC series introduced corrupted
|
||||
# lockfiles. There were two major problems:
|
||||
#
|
||||
# * multiple copies of the same GIT section appeared in the lockfile
|
||||
# * when this happened, those sections got multiple copies of gems
|
||||
# in those sections.
|
||||
#
|
||||
# As a result, Bundler 1.1 contains code that fixes the earlier
|
||||
# corruption. We will remove this fix-up code in Bundler 1.2.
|
||||
|
||||
module Bundler
|
||||
class LockfileParser
|
||||
attr_reader :sources, :dependencies, :specs, :platforms, :bundler_version, :ruby_version
|
||||
|
||||
BUNDLED = "BUNDLED WITH".freeze
|
||||
DEPENDENCIES = "DEPENDENCIES".freeze
|
||||
PLATFORMS = "PLATFORMS".freeze
|
||||
RUBY = "RUBY VERSION".freeze
|
||||
GIT = "GIT".freeze
|
||||
GEM = "GEM".freeze
|
||||
PATH = "PATH".freeze
|
||||
PLUGIN = "PLUGIN SOURCE".freeze
|
||||
SPECS = " specs:".freeze
|
||||
OPTIONS = /^ ([a-z]+): (.*)$/i
|
||||
SOURCE = [GIT, GEM, PATH, PLUGIN].freeze
|
||||
|
||||
SECTIONS_BY_VERSION_INTRODUCED = {
|
||||
# The strings have to be dup'ed for old RG on Ruby 2.3+
|
||||
# TODO: remove dup in Bundler 2.0
|
||||
Gem::Version.create("1.0".dup) => [DEPENDENCIES, PLATFORMS, GIT, GEM, PATH].freeze,
|
||||
Gem::Version.create("1.10".dup) => [BUNDLED].freeze,
|
||||
Gem::Version.create("1.12".dup) => [RUBY].freeze,
|
||||
Gem::Version.create("1.13".dup) => [PLUGIN].freeze,
|
||||
}.freeze
|
||||
|
||||
KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten.freeze
|
||||
|
||||
ENVIRONMENT_VERSION_SECTIONS = [BUNDLED, RUBY].freeze
|
||||
|
||||
def self.sections_in_lockfile(lockfile_contents)
|
||||
lockfile_contents.scan(/^\w[\w ]*$/).uniq
|
||||
end
|
||||
|
||||
def self.unknown_sections_in_lockfile(lockfile_contents)
|
||||
sections_in_lockfile(lockfile_contents) - KNOWN_SECTIONS
|
||||
end
|
||||
|
||||
def self.sections_to_ignore(base_version = nil)
|
||||
base_version &&= base_version.release
|
||||
base_version ||= Gem::Version.create("1.0".dup)
|
||||
attributes = []
|
||||
SECTIONS_BY_VERSION_INTRODUCED.each do |version, introduced|
|
||||
next if version <= base_version
|
||||
attributes += introduced
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
def initialize(lockfile)
|
||||
@platforms = []
|
||||
@sources = []
|
||||
@dependencies = {}
|
||||
@state = nil
|
||||
@specs = {}
|
||||
|
||||
@rubygems_aggregate = Source::Rubygems.new
|
||||
|
||||
if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
|
||||
raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \
|
||||
"Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock."
|
||||
end
|
||||
|
||||
lockfile.split(/(?:\r?\n)+/).each do |line|
|
||||
if SOURCE.include?(line)
|
||||
@state = :source
|
||||
parse_source(line)
|
||||
elsif line == DEPENDENCIES
|
||||
@state = :dependency
|
||||
elsif line == PLATFORMS
|
||||
@state = :platform
|
||||
elsif line == RUBY
|
||||
@state = :ruby
|
||||
elsif line == BUNDLED
|
||||
@state = :bundled_with
|
||||
elsif line =~ /^[^\s]/
|
||||
@state = nil
|
||||
elsif @state
|
||||
send("parse_#{@state}", line)
|
||||
end
|
||||
end
|
||||
@sources << @rubygems_aggregate unless Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
|
||||
@specs = @specs.values.sort_by(&:identifier)
|
||||
warn_for_outdated_bundler_version
|
||||
rescue ArgumentError => e
|
||||
Bundler.ui.debug(e)
|
||||
raise LockfileError, "Your lockfile is unreadable. Run `rm #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` " \
|
||||
"and then `bundle install` to generate a new lockfile."
|
||||
end
|
||||
|
||||
def warn_for_outdated_bundler_version
|
||||
return unless bundler_version
|
||||
prerelease_text = bundler_version.prerelease? ? " --pre" : ""
|
||||
current_version = Gem::Version.create(Bundler::VERSION)
|
||||
case current_version.segments.first <=> bundler_version.segments.first
|
||||
when -1
|
||||
raise LockfileError, "You must use Bundler #{bundler_version.segments.first} or greater with this lockfile."
|
||||
when 0
|
||||
if current_version < bundler_version
|
||||
Bundler.ui.warn "Warning: the running version of Bundler (#{current_version}) is older " \
|
||||
"than the version that created the lockfile (#{bundler_version}). We suggest you " \
|
||||
"upgrade to the latest version of Bundler by running `gem " \
|
||||
"install bundler#{prerelease_text}`.\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
TYPES = {
|
||||
GIT => Bundler::Source::Git,
|
||||
GEM => Bundler::Source::Rubygems,
|
||||
PATH => Bundler::Source::Path,
|
||||
PLUGIN => Bundler::Plugin,
|
||||
}.freeze
|
||||
|
||||
def parse_source(line)
|
||||
case line
|
||||
when SPECS
|
||||
case @type
|
||||
when PATH
|
||||
@current_source = TYPES[@type].from_lock(@opts)
|
||||
@sources << @current_source
|
||||
when GIT
|
||||
@current_source = TYPES[@type].from_lock(@opts)
|
||||
# Strip out duplicate GIT sections
|
||||
if @sources.include?(@current_source)
|
||||
@current_source = @sources.find {|s| s == @current_source }
|
||||
else
|
||||
@sources << @current_source
|
||||
end
|
||||
when GEM
|
||||
if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
|
||||
@opts["remotes"] = @opts.delete("remote")
|
||||
@current_source = TYPES[@type].from_lock(@opts)
|
||||
@sources << @current_source
|
||||
else
|
||||
Array(@opts["remote"]).each do |url|
|
||||
@rubygems_aggregate.add_remote(url)
|
||||
end
|
||||
@current_source = @rubygems_aggregate
|
||||
end
|
||||
when PLUGIN
|
||||
@current_source = Plugin.source_from_lock(@opts)
|
||||
@sources << @current_source
|
||||
end
|
||||
when OPTIONS
|
||||
value = $2
|
||||
value = true if value == "true"
|
||||
value = false if value == "false"
|
||||
|
||||
key = $1
|
||||
|
||||
if @opts[key]
|
||||
@opts[key] = Array(@opts[key])
|
||||
@opts[key] << value
|
||||
else
|
||||
@opts[key] = value
|
||||
end
|
||||
when *SOURCE
|
||||
@current_source = nil
|
||||
@opts = {}
|
||||
@type = line
|
||||
else
|
||||
parse_spec(line)
|
||||
end
|
||||
end
|
||||
|
||||
space = / /
|
||||
NAME_VERSION = /
|
||||
^(#{space}{2}|#{space}{4}|#{space}{6})(?!#{space}) # Exactly 2, 4, or 6 spaces at the start of the line
|
||||
(.*?) # Name
|
||||
(?:#{space}\(([^-]*) # Space, followed by version
|
||||
(?:-(.*))?\))? # Optional platform
|
||||
(!)? # Optional pinned marker
|
||||
$ # Line end
|
||||
/xo
|
||||
|
||||
def parse_dependency(line)
|
||||
return unless line =~ NAME_VERSION
|
||||
spaces = $1
|
||||
return unless spaces.size == 2
|
||||
name = $2
|
||||
version = $3
|
||||
pinned = $5
|
||||
|
||||
version = version.split(",").map(&:strip) if version
|
||||
|
||||
dep = Bundler::Dependency.new(name, version)
|
||||
|
||||
if pinned && dep.name != "bundler"
|
||||
spec = @specs.find {|_, v| v.name == dep.name }
|
||||
dep.source = spec.last.source if spec
|
||||
|
||||
# Path sources need to know what the default name / version
|
||||
# to use in the case that there are no gemspecs present. A fake
|
||||
# gemspec is created based on the version set on the dependency
|
||||
# TODO: Use the version from the spec instead of from the dependency
|
||||
if version && version.size == 1 && version.first =~ /^\s*= (.+)\s*$/ && dep.source.is_a?(Bundler::Source::Path)
|
||||
dep.source.name = name
|
||||
dep.source.version = $1
|
||||
end
|
||||
end
|
||||
|
||||
@dependencies[dep.name] = dep
|
||||
end
|
||||
|
||||
def parse_spec(line)
|
||||
return unless line =~ NAME_VERSION
|
||||
spaces = $1
|
||||
name = $2
|
||||
version = $3
|
||||
platform = $4
|
||||
|
||||
if spaces.size == 4
|
||||
version = Gem::Version.new(version)
|
||||
platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
|
||||
@current_spec = LazySpecification.new(name, version, platform)
|
||||
@current_spec.source = @current_source
|
||||
|
||||
# Avoid introducing multiple copies of the same spec (caused by
|
||||
# duplicate GIT sections)
|
||||
@specs[@current_spec.identifier] ||= @current_spec
|
||||
elsif spaces.size == 6
|
||||
version = version.split(",").map(&:strip) if version
|
||||
dep = Gem::Dependency.new(name, version)
|
||||
@current_spec.dependencies << dep
|
||||
end
|
||||
end
|
||||
|
||||
def parse_platform(line)
|
||||
@platforms << Gem::Platform.new($1) if line =~ /^ (.*)$/
|
||||
end
|
||||
|
||||
def parse_bundled_with(line)
|
||||
line = line.strip
|
||||
return unless Gem::Version.correct?(line)
|
||||
@bundler_version = Gem::Version.create(line)
|
||||
end
|
||||
|
||||
def parse_ruby(line)
|
||||
@ruby_version = line.strip
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/gem_helpers"
|
||||
|
||||
module Bundler
|
||||
module MatchPlatform
|
||||
include GemHelpers
|
||||
|
||||
def match_platform(p)
|
||||
MatchPlatform.platforms_match?(platform, p)
|
||||
end
|
||||
|
||||
def self.platforms_match?(gemspec_platform, local_platform)
|
||||
return true if gemspec_platform.nil?
|
||||
return true if Gem::Platform::RUBY == gemspec_platform
|
||||
return true if local_platform == gemspec_platform
|
||||
gemspec_platform = Gem::Platform.new(gemspec_platform)
|
||||
return true if GemHelpers.generic(gemspec_platform) === local_platform
|
||||
return true if gemspec_platform === local_platform
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,223 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "socket"
|
||||
|
||||
module Bundler
|
||||
class Settings
|
||||
# Class used to build the mirror set and then find a mirror for a given URI
|
||||
#
|
||||
# @param prober [Prober object, nil] by default a TCPSocketProbe, this object
|
||||
# will be used to probe the mirror address to validate that the mirror replies.
|
||||
class Mirrors
|
||||
def initialize(prober = nil)
|
||||
@all = Mirror.new
|
||||
@prober = prober || TCPSocketProbe.new
|
||||
@mirrors = {}
|
||||
end
|
||||
|
||||
# Returns a mirror for the given uri.
|
||||
#
|
||||
# Depending on the uri having a valid mirror or not, it may be a
|
||||
# mirror that points to the provided uri
|
||||
def for(uri)
|
||||
if @all.validate!(@prober).valid?
|
||||
@all
|
||||
else
|
||||
fetch_valid_mirror_for(Settings.normalize_uri(uri))
|
||||
end
|
||||
end
|
||||
|
||||
def each
|
||||
@mirrors.each do |k, v|
|
||||
yield k, v.uri.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def parse(key, value)
|
||||
config = MirrorConfig.new(key, value)
|
||||
mirror = if config.all?
|
||||
@all
|
||||
else
|
||||
@mirrors[config.uri] ||= Mirror.new
|
||||
end
|
||||
config.update_mirror(mirror)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_valid_mirror_for(uri)
|
||||
downcased = uri.to_s.downcase
|
||||
mirror = @mirrors[downcased] || @mirrors[URI(downcased).host] || Mirror.new(uri)
|
||||
mirror.validate!(@prober)
|
||||
mirror = Mirror.new(uri) unless mirror.valid?
|
||||
mirror
|
||||
end
|
||||
end
|
||||
|
||||
# A mirror
|
||||
#
|
||||
# Contains both the uri that should be used as a mirror and the
|
||||
# fallback timeout which will be used for probing if the mirror
|
||||
# replies on time or not.
|
||||
class Mirror
|
||||
DEFAULT_FALLBACK_TIMEOUT = 0.1
|
||||
|
||||
attr_reader :uri, :fallback_timeout
|
||||
|
||||
def initialize(uri = nil, fallback_timeout = 0)
|
||||
self.uri = uri
|
||||
self.fallback_timeout = fallback_timeout
|
||||
@valid = nil
|
||||
end
|
||||
|
||||
def uri=(uri)
|
||||
@uri = if uri.nil?
|
||||
nil
|
||||
else
|
||||
URI(uri.to_s)
|
||||
end
|
||||
@valid = nil
|
||||
end
|
||||
|
||||
def fallback_timeout=(timeout)
|
||||
case timeout
|
||||
when true, "true"
|
||||
@fallback_timeout = DEFAULT_FALLBACK_TIMEOUT
|
||||
when false, "false"
|
||||
@fallback_timeout = 0
|
||||
else
|
||||
@fallback_timeout = timeout.to_i
|
||||
end
|
||||
@valid = nil
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
!other.nil? && uri == other.uri && fallback_timeout == other.fallback_timeout
|
||||
end
|
||||
|
||||
def valid?
|
||||
return false if @uri.nil?
|
||||
return @valid unless @valid.nil?
|
||||
false
|
||||
end
|
||||
|
||||
def validate!(probe = nil)
|
||||
@valid = false if uri.nil?
|
||||
if @valid.nil?
|
||||
@valid = fallback_timeout == 0 || (probe || TCPSocketProbe.new).replies?(self)
|
||||
end
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# Class used to parse one configuration line
|
||||
#
|
||||
# Gets the configuration line and the value.
|
||||
# This object provides a `update_mirror` method
|
||||
# used to setup the given mirror value.
|
||||
class MirrorConfig
|
||||
attr_accessor :uri, :value
|
||||
|
||||
def initialize(config_line, value)
|
||||
uri, fallback =
|
||||
config_line.match(%r{\Amirror\.(all|.+?)(\.fallback_timeout)?\/?\z}).captures
|
||||
@fallback = !fallback.nil?
|
||||
@all = false
|
||||
if uri == "all"
|
||||
@all = true
|
||||
else
|
||||
@uri = URI(uri).absolute? ? Settings.normalize_uri(uri) : uri
|
||||
end
|
||||
@value = value
|
||||
end
|
||||
|
||||
def all?
|
||||
@all
|
||||
end
|
||||
|
||||
def update_mirror(mirror)
|
||||
if @fallback
|
||||
mirror.fallback_timeout = @value
|
||||
else
|
||||
mirror.uri = Settings.normalize_uri(@value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Class used for probing TCP availability for a given mirror.
|
||||
class TCPSocketProbe
|
||||
def replies?(mirror)
|
||||
MirrorSockets.new(mirror).any? do |socket, address, timeout|
|
||||
begin
|
||||
socket.connect_nonblock(address)
|
||||
rescue Errno::EINPROGRESS
|
||||
wait_for_writtable_socket(socket, address, timeout)
|
||||
rescue RuntimeError # Connection failed somehow, again
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def wait_for_writtable_socket(socket, address, timeout)
|
||||
if IO.select(nil, [socket], nil, timeout)
|
||||
probe_writtable_socket(socket, address)
|
||||
else # TCP Handshake timed out, or there is something dropping packets
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def probe_writtable_socket(socket, address)
|
||||
socket.connect_nonblock(address)
|
||||
rescue Errno::EISCONN
|
||||
true
|
||||
rescue StandardError # Connection failed
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Class used to build the list of sockets that correspond to
|
||||
# a given mirror.
|
||||
#
|
||||
# One mirror may correspond to many different addresses, both
|
||||
# because of it having many dns entries or because
|
||||
# the network interface is both ipv4 and ipv5
|
||||
class MirrorSockets
|
||||
def initialize(mirror)
|
||||
@timeout = mirror.fallback_timeout
|
||||
@addresses = Socket.getaddrinfo(mirror.uri.host, mirror.uri.port).map do |address|
|
||||
SocketAddress.new(address[0], address[3], address[1])
|
||||
end
|
||||
end
|
||||
|
||||
def any?
|
||||
@addresses.any? do |address|
|
||||
socket = Socket.new(Socket.const_get(address.type), Socket::SOCK_STREAM, 0)
|
||||
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
||||
value = yield socket, address.to_socket_address, @timeout
|
||||
socket.close unless socket.closed?
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Socket address builder.
|
||||
#
|
||||
# Given a socket type, a host and a port,
|
||||
# provides a method to build sockaddr string
|
||||
class SocketAddress
|
||||
attr_reader :type, :host, :port
|
||||
|
||||
def initialize(type, host, port)
|
||||
@type = type
|
||||
@host = host
|
||||
@port = port
|
||||
end
|
||||
|
||||
def to_socket_address
|
||||
Socket.pack_sockaddr_in(@port, @host)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,292 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/plugin/api"
|
||||
|
||||
module Bundler
|
||||
module Plugin
|
||||
autoload :DSL, "bundler/plugin/dsl"
|
||||
autoload :Events, "bundler/plugin/events"
|
||||
autoload :Index, "bundler/plugin/index"
|
||||
autoload :Installer, "bundler/plugin/installer"
|
||||
autoload :SourceList, "bundler/plugin/source_list"
|
||||
|
||||
class MalformattedPlugin < PluginError; end
|
||||
class UndefinedCommandError < PluginError; end
|
||||
class UnknownSourceError < PluginError; end
|
||||
|
||||
PLUGIN_FILE_NAME = "plugins.rb".freeze
|
||||
|
||||
module_function
|
||||
|
||||
def reset!
|
||||
instance_variables.each {|i| remove_instance_variable(i) }
|
||||
|
||||
@sources = {}
|
||||
@commands = {}
|
||||
@hooks_by_event = Hash.new {|h, k| h[k] = [] }
|
||||
@loaded_plugin_names = []
|
||||
end
|
||||
|
||||
reset!
|
||||
|
||||
# Installs a new plugin by the given name
|
||||
#
|
||||
# @param [Array<String>] names the name of plugin to be installed
|
||||
# @param [Hash] options various parameters as described in description.
|
||||
# Refer to cli/plugin for available options
|
||||
def install(names, options)
|
||||
specs = Installer.new.install(names, options)
|
||||
|
||||
save_plugins names, specs
|
||||
rescue PluginError => e
|
||||
if specs
|
||||
specs_to_delete = Hash[specs.select {|k, _v| names.include?(k) && !index.commands.values.include?(k) }]
|
||||
specs_to_delete.values.each {|spec| Bundler.rm_rf(spec.full_gem_path) }
|
||||
end
|
||||
|
||||
Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n #{e.backtrace.join("\n ")}"
|
||||
end
|
||||
|
||||
# Evaluates the Gemfile with a limited DSL and installs the plugins
|
||||
# specified by plugin method
|
||||
#
|
||||
# @param [Pathname] gemfile path
|
||||
# @param [Proc] block that can be evaluated for (inline) Gemfile
|
||||
def gemfile_install(gemfile = nil, &inline)
|
||||
builder = DSL.new
|
||||
if block_given?
|
||||
builder.instance_eval(&inline)
|
||||
else
|
||||
builder.eval_gemfile(gemfile)
|
||||
end
|
||||
definition = builder.to_definition(nil, true)
|
||||
|
||||
return if definition.dependencies.empty?
|
||||
|
||||
plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p }
|
||||
installed_specs = Installer.new.install_definition(definition)
|
||||
|
||||
save_plugins plugins, installed_specs, builder.inferred_plugins
|
||||
rescue RuntimeError => e
|
||||
unless e.is_a?(GemfileError)
|
||||
Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}"
|
||||
end
|
||||
raise
|
||||
end
|
||||
|
||||
# The index object used to store the details about the plugin
|
||||
def index
|
||||
@index ||= Index.new
|
||||
end
|
||||
|
||||
# The directory root for all plugin related data
|
||||
#
|
||||
# If run in an app, points to local root, in app_config_path
|
||||
# Otherwise, points to global root, in Bundler.user_bundle_path("plugin")
|
||||
def root
|
||||
@root ||= if SharedHelpers.in_bundle?
|
||||
local_root
|
||||
else
|
||||
global_root
|
||||
end
|
||||
end
|
||||
|
||||
def local_root
|
||||
Bundler.app_config_path.join("plugin")
|
||||
end
|
||||
|
||||
# The global directory root for all plugin related data
|
||||
def global_root
|
||||
Bundler.user_bundle_path("plugin")
|
||||
end
|
||||
|
||||
# The cache directory for plugin stuffs
|
||||
def cache
|
||||
@cache ||= root.join("cache")
|
||||
end
|
||||
|
||||
# To be called via the API to register to handle a command
|
||||
def add_command(command, cls)
|
||||
@commands[command] = cls
|
||||
end
|
||||
|
||||
# Checks if any plugin handles the command
|
||||
def command?(command)
|
||||
!index.command_plugin(command).nil?
|
||||
end
|
||||
|
||||
# To be called from Cli class to pass the command and argument to
|
||||
# approriate plugin class
|
||||
def exec_command(command, args)
|
||||
raise UndefinedCommandError, "Command `#{command}` not found" unless command? command
|
||||
|
||||
load_plugin index.command_plugin(command) unless @commands.key? command
|
||||
|
||||
@commands[command].new.exec(command, args)
|
||||
end
|
||||
|
||||
# To be called via the API to register to handle a source plugin
|
||||
def add_source(source, cls)
|
||||
@sources[source] = cls
|
||||
end
|
||||
|
||||
# Checks if any plugin declares the source
|
||||
def source?(name)
|
||||
!index.source_plugin(name.to_s).nil?
|
||||
end
|
||||
|
||||
# @return [Class] that handles the source. The calss includes API::Source
|
||||
def source(name)
|
||||
raise UnknownSourceError, "Source #{name} not found" unless source? name
|
||||
|
||||
load_plugin(index.source_plugin(name)) unless @sources.key? name
|
||||
|
||||
@sources[name]
|
||||
end
|
||||
|
||||
# @param [Hash] The options that are present in the lock file
|
||||
# @return [API::Source] the instance of the class that handles the source
|
||||
# type passed in locked_opts
|
||||
def source_from_lock(locked_opts)
|
||||
src = source(locked_opts["type"])
|
||||
|
||||
src.new(locked_opts.merge("uri" => locked_opts["remote"]))
|
||||
end
|
||||
|
||||
# To be called via the API to register a hooks and corresponding block that
|
||||
# will be called to handle the hook
|
||||
def add_hook(event, &block)
|
||||
unless Events.defined_event?(event)
|
||||
raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events"
|
||||
end
|
||||
@hooks_by_event[event.to_s] << block
|
||||
end
|
||||
|
||||
# Runs all the hooks that are registered for the passed event
|
||||
#
|
||||
# It passes the passed arguments and block to the block registered with
|
||||
# the api.
|
||||
#
|
||||
# @param [String] event
|
||||
def hook(event, *args, &arg_blk)
|
||||
return unless Bundler.feature_flag.plugins?
|
||||
unless Events.defined_event?(event)
|
||||
raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events"
|
||||
end
|
||||
|
||||
plugins = index.hook_plugins(event)
|
||||
return unless plugins.any?
|
||||
|
||||
(plugins - @loaded_plugin_names).each {|name| load_plugin(name) }
|
||||
|
||||
@hooks_by_event[event].each {|blk| blk.call(*args, &arg_blk) }
|
||||
end
|
||||
|
||||
# currently only intended for specs
|
||||
#
|
||||
# @return [String, nil] installed path
|
||||
def installed?(plugin)
|
||||
Index.new.installed?(plugin)
|
||||
end
|
||||
|
||||
# Post installation processing and registering with index
|
||||
#
|
||||
# @param [Array<String>] plugins list to be installed
|
||||
# @param [Hash] specs of plugins mapped to installation path (currently they
|
||||
# contain all the installed specs, including plugins)
|
||||
# @param [Array<String>] names of inferred source plugins that can be ignored
|
||||
def save_plugins(plugins, specs, optional_plugins = [])
|
||||
plugins.each do |name|
|
||||
spec = specs[name]
|
||||
validate_plugin! Pathname.new(spec.full_gem_path)
|
||||
installed = register_plugin(name, spec, optional_plugins.include?(name))
|
||||
Bundler.ui.info "Installed plugin #{name}" if installed
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if the gem is good to be a plugin
|
||||
#
|
||||
# At present it only checks whether it contains plugins.rb file
|
||||
#
|
||||
# @param [Pathname] plugin_path the path plugin is installed at
|
||||
# @raise [MalformattedPlugin] if plugins.rb file is not found
|
||||
def validate_plugin!(plugin_path)
|
||||
plugin_file = plugin_path.join(PLUGIN_FILE_NAME)
|
||||
raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin." unless plugin_file.file?
|
||||
end
|
||||
|
||||
# Runs the plugins.rb file in an isolated namespace, records the plugin
|
||||
# actions it registers for and then passes the data to index to be stored.
|
||||
#
|
||||
# @param [String] name the name of the plugin
|
||||
# @param [Specification] spec of installed plugin
|
||||
# @param [Boolean] optional_plugin, removed if there is conflict with any
|
||||
# other plugin (used for default source plugins)
|
||||
#
|
||||
# @raise [MalformattedPlugin] if plugins.rb raises any error
|
||||
def register_plugin(name, spec, optional_plugin = false)
|
||||
commands = @commands
|
||||
sources = @sources
|
||||
hooks = @hooks_by_event
|
||||
|
||||
@commands = {}
|
||||
@sources = {}
|
||||
@hooks_by_event = Hash.new {|h, k| h[k] = [] }
|
||||
|
||||
load_paths = spec.load_paths
|
||||
add_to_load_path(load_paths)
|
||||
path = Pathname.new spec.full_gem_path
|
||||
|
||||
begin
|
||||
load path.join(PLUGIN_FILE_NAME), true
|
||||
rescue StandardError => e
|
||||
raise MalformattedPlugin, "#{e.class}: #{e.message}"
|
||||
end
|
||||
|
||||
if optional_plugin && @sources.keys.any? {|s| source? s }
|
||||
Bundler.rm_rf(path)
|
||||
false
|
||||
else
|
||||
index.register_plugin(name, path.to_s, load_paths, @commands.keys,
|
||||
@sources.keys, @hooks_by_event.keys)
|
||||
true
|
||||
end
|
||||
ensure
|
||||
@commands = commands
|
||||
@sources = sources
|
||||
@hooks_by_event = hooks
|
||||
end
|
||||
|
||||
# Executes the plugins.rb file
|
||||
#
|
||||
# @param [String] name of the plugin
|
||||
def load_plugin(name)
|
||||
# Need to ensure before this that plugin root where the rest of gems
|
||||
# are installed to be on load path to support plugin deps. Currently not
|
||||
# done to avoid conflicts
|
||||
path = index.plugin_path(name)
|
||||
|
||||
add_to_load_path(index.load_paths(name))
|
||||
|
||||
load path.join(PLUGIN_FILE_NAME)
|
||||
|
||||
@loaded_plugin_names << name
|
||||
rescue RuntimeError => e
|
||||
Bundler.ui.error "Failed loading plugin #{name}: #{e.message}"
|
||||
raise
|
||||
end
|
||||
|
||||
def add_to_load_path(load_paths)
|
||||
if insert_index = Bundler.rubygems.load_path_insert_index
|
||||
$LOAD_PATH.insert(insert_index, *load_paths)
|
||||
else
|
||||
$LOAD_PATH.unshift(*load_paths)
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
private :load_plugin, :register_plugin, :save_plugins, :validate_plugin!,
|
||||
:add_to_load_path
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# This is the interfacing class represents the API that we intend to provide
|
||||
# the plugins to use.
|
||||
#
|
||||
# For plugins to be independent of the Bundler internals they shall limit their
|
||||
# interactions to methods of this class only. This will save them from breaking
|
||||
# when some internal change.
|
||||
#
|
||||
# Currently we are delegating the methods defined in Bundler class to
|
||||
# itself. So, this class acts as a buffer.
|
||||
#
|
||||
# If there is some change in the Bundler class that is incompatible to its
|
||||
# previous behavior or if otherwise desired, we can reimplement(or implement)
|
||||
# the method to preserve compatibility.
|
||||
#
|
||||
# To use this, either the class can inherit this class or use it directly.
|
||||
# For example of both types of use, refer the file `spec/plugins/command.rb`
|
||||
#
|
||||
# To use it without inheriting, you will have to create an object of this
|
||||
# to use the functions (except for declaration functions like command, source,
|
||||
# and hooks).
|
||||
module Plugin
|
||||
class API
|
||||
autoload :Source, "bundler/plugin/api/source"
|
||||
|
||||
# The plugins should declare that they handle a command through this helper.
|
||||
#
|
||||
# @param [String] command being handled by them
|
||||
# @param [Class] (optional) class that handles the command. If not
|
||||
# provided, the `self` class will be used.
|
||||
def self.command(command, cls = self)
|
||||
Plugin.add_command command, cls
|
||||
end
|
||||
|
||||
# The plugins should declare that they provide a installation source
|
||||
# through this helper.
|
||||
#
|
||||
# @param [String] the source type they provide
|
||||
# @param [Class] (optional) class that handles the source. If not
|
||||
# provided, the `self` class will be used.
|
||||
def self.source(source, cls = self)
|
||||
cls.send :include, Bundler::Plugin::API::Source
|
||||
Plugin.add_source source, cls
|
||||
end
|
||||
|
||||
def self.hook(event, &block)
|
||||
Plugin.add_hook(event, &block)
|
||||
end
|
||||
|
||||
# The cache dir to be used by the plugins for storage
|
||||
#
|
||||
# @return [Pathname] path of the cache dir
|
||||
def cache_dir
|
||||
Plugin.cache.join("plugins")
|
||||
end
|
||||
|
||||
# A tmp dir to be used by plugins
|
||||
# Accepts names that get concatenated as suffix
|
||||
#
|
||||
# @return [Pathname] object for the new directory created
|
||||
def tmp(*names)
|
||||
Bundler.tmp(["plugin", *names].join("-"))
|
||||
end
|
||||
|
||||
def method_missing(name, *args, &blk)
|
||||
return Bundler.send(name, *args, &blk) if Bundler.respond_to?(name)
|
||||
|
||||
return SharedHelpers.send(name, *args, &blk) if SharedHelpers.respond_to?(name)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def respond_to_missing?(name, include_private = false)
|
||||
SharedHelpers.respond_to?(name, include_private) ||
|
||||
Bundler.respond_to?(name, include_private) || super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,306 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "uri"
|
||||
|
||||
module Bundler
|
||||
module Plugin
|
||||
class API
|
||||
# This class provides the base to build source plugins
|
||||
# All the method here are required to build a source plugin (except
|
||||
# `uri_hash`, `gem_install_dir`; they are helpers).
|
||||
#
|
||||
# Defaults for methods, where ever possible are provided which is
|
||||
# expected to work. But, all source plugins have to override
|
||||
# `fetch_gemspec_files` and `install`. Defaults are also not provided for
|
||||
# `remote!`, `cache!` and `unlock!`.
|
||||
#
|
||||
# The defaults shall work for most situations but nevertheless they can
|
||||
# be (preferably should be) overridden as per the plugins' needs safely
|
||||
# (as long as they behave as expected).
|
||||
# On overriding `initialize` you should call super first.
|
||||
#
|
||||
# If required plugin should override `hash`, `==` and `eql?` methods to be
|
||||
# able to match objects representing same sources, but may be created in
|
||||
# different situation (like form gemfile and lockfile). The default ones
|
||||
# checks only for class and uri, but elaborate source plugins may need
|
||||
# more comparisons (e.g. git checking on branch or tag).
|
||||
#
|
||||
# @!attribute [r] uri
|
||||
# @return [String] the remote specified with `source` block in Gemfile
|
||||
#
|
||||
# @!attribute [r] options
|
||||
# @return [String] options passed during initialization (either from
|
||||
# lockfile or Gemfile)
|
||||
#
|
||||
# @!attribute [r] name
|
||||
# @return [String] name that can be used to uniquely identify a source
|
||||
#
|
||||
# @!attribute [rw] dependency_names
|
||||
# @return [Array<String>] Names of dependencies that the source should
|
||||
# try to resolve. It is not necessary to use this list intenally. This
|
||||
# is present to be compatible with `Definition` and is used by
|
||||
# rubygems source.
|
||||
module Source
|
||||
attr_reader :uri, :options, :name
|
||||
attr_accessor :dependency_names
|
||||
|
||||
def initialize(opts)
|
||||
@options = opts
|
||||
@dependency_names = []
|
||||
@uri = opts["uri"]
|
||||
@type = opts["type"]
|
||||
@name = opts["name"] || "#{@type} at #{@uri}"
|
||||
end
|
||||
|
||||
# This is used by the default `spec` method to constructs the
|
||||
# Specification objects for the gems and versions that can be installed
|
||||
# by this source plugin.
|
||||
#
|
||||
# Note: If the spec method is overridden, this function is not necessary
|
||||
#
|
||||
# @return [Array<String>] paths of the gemspec files for gems that can
|
||||
# be installed
|
||||
def fetch_gemspec_files
|
||||
[]
|
||||
end
|
||||
|
||||
# Options to be saved in the lockfile so that the source plugin is able
|
||||
# to check out same version of gem later.
|
||||
#
|
||||
# There options are passed when the source plugin is created from the
|
||||
# lock file.
|
||||
#
|
||||
# @return [Hash]
|
||||
def options_to_lock
|
||||
{}
|
||||
end
|
||||
|
||||
# Install the gem specified by the spec at appropriate path.
|
||||
# `install_path` provides a sufficient default, if the source can only
|
||||
# satisfy one gem, but is not binding.
|
||||
#
|
||||
# @return [String] post installation message (if any)
|
||||
def install(spec, opts)
|
||||
raise MalformattedPlugin, "Source plugins need to override the install method."
|
||||
end
|
||||
|
||||
# It builds extensions, generates bins and installs them for the spec
|
||||
# provided.
|
||||
#
|
||||
# It depends on `spec.loaded_from` to get full_gem_path. The source
|
||||
# plugins should set that.
|
||||
#
|
||||
# It should be called in `install` after the plugin is done placing the
|
||||
# gem at correct install location.
|
||||
#
|
||||
# It also runs Gem hooks `pre_install`, `post_build` and `post_install`
|
||||
#
|
||||
# Note: Do not override if you don't know what you are doing.
|
||||
def post_install(spec, disable_exts = false)
|
||||
opts = { :env_shebang => false, :disable_extensions => disable_exts }
|
||||
installer = Bundler::Source::Path::Installer.new(spec, opts)
|
||||
installer.post_install
|
||||
end
|
||||
|
||||
# A default installation path to install a single gem. If the source
|
||||
# servers multiple gems, it's not of much use and the source should one
|
||||
# of its own.
|
||||
def install_path
|
||||
@install_path ||=
|
||||
begin
|
||||
base_name = File.basename(URI.parse(uri).normalize.path)
|
||||
|
||||
gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
|
||||
end
|
||||
end
|
||||
|
||||
# Parses the gemspec files to find the specs for the gems that can be
|
||||
# satisfied by the source.
|
||||
#
|
||||
# Few important points to keep in mind:
|
||||
# - If the gems are not installed then it shall return specs for all
|
||||
# the gems it can satisfy
|
||||
# - If gem is installed (that is to be detected by the plugin itself)
|
||||
# then it shall return at least the specs that are installed.
|
||||
# - The `loaded_from` for each of the specs shall be correct (it is
|
||||
# used to find the load path)
|
||||
#
|
||||
# @return [Bundler::Index] index containing the specs
|
||||
def specs
|
||||
files = fetch_gemspec_files
|
||||
|
||||
Bundler::Index.build do |index|
|
||||
files.each do |file|
|
||||
next unless spec = Bundler.load_gemspec(file)
|
||||
Bundler.rubygems.set_installed_by_version(spec)
|
||||
|
||||
spec.source = self
|
||||
Bundler.rubygems.validate(spec)
|
||||
|
||||
index << spec
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Set internal representation to fetch the gems/specs from remote.
|
||||
#
|
||||
# When this is called, the source should try to fetch the specs and
|
||||
# install from remote path.
|
||||
def remote!
|
||||
end
|
||||
|
||||
# Set internal representation to fetch the gems/specs from app cache.
|
||||
#
|
||||
# When this is called, the source should try to fetch the specs and
|
||||
# install from the path provided by `app_cache_path`.
|
||||
def cached!
|
||||
end
|
||||
|
||||
# This is called to update the spec and installation.
|
||||
#
|
||||
# If the source plugin is loaded from lockfile or otherwise, it shall
|
||||
# refresh the cache/specs (e.g. git sources can make a fresh clone).
|
||||
def unlock!
|
||||
end
|
||||
|
||||
# Name of directory where plugin the is expected to cache the gems when
|
||||
# #cache is called.
|
||||
#
|
||||
# Also this name is matched against the directories in cache for pruning
|
||||
#
|
||||
# This is used by `app_cache_path`
|
||||
def app_cache_dirname
|
||||
base_name = File.basename(URI.parse(uri).normalize.path)
|
||||
"#{base_name}-#{uri_hash}"
|
||||
end
|
||||
|
||||
# This method is called while caching to save copy of the gems that the
|
||||
# source can resolve to path provided by `app_cache_app`so that they can
|
||||
# be reinstalled from the cache without querying the remote (i.e. an
|
||||
# alternative to remote)
|
||||
#
|
||||
# This is stored with the app and source plugins should try to provide
|
||||
# specs and install only from this cache when `cached!` is called.
|
||||
#
|
||||
# This cache is different from the internal caching that can be done
|
||||
# at sub paths of `cache_path` (from API). This can be though as caching
|
||||
# by bundler.
|
||||
def cache(spec, custom_path = nil)
|
||||
new_cache_path = app_cache_path(custom_path)
|
||||
|
||||
FileUtils.rm_rf(new_cache_path)
|
||||
FileUtils.cp_r(install_path, new_cache_path)
|
||||
FileUtils.touch(app_cache_path.join(".bundlecache"))
|
||||
end
|
||||
|
||||
# This shall check if two source object represent the same source.
|
||||
#
|
||||
# The comparison shall take place only on the attribute that can be
|
||||
# inferred from the options passed from Gemfile and not on attibutes
|
||||
# that are used to pin down the gem to specific version (e.g. Git
|
||||
# sources should compare on branch and tag but not on commit hash)
|
||||
#
|
||||
# The sources objects are constructed from Gemfile as well as from
|
||||
# lockfile. To converge the sources, it is necessary that they match.
|
||||
#
|
||||
# The same applies for `eql?` and `hash`
|
||||
def ==(other)
|
||||
other.is_a?(self.class) && uri == other.uri
|
||||
end
|
||||
|
||||
# When overriding `eql?` please preserve the behaviour as mentioned in
|
||||
# docstring for `==` method.
|
||||
alias_method :eql?, :==
|
||||
|
||||
# When overriding `hash` please preserve the behaviour as mentioned in
|
||||
# docstring for `==` method, i.e. two methods equal by above comparison
|
||||
# should have same hash.
|
||||
def hash
|
||||
[self.class, uri].hash
|
||||
end
|
||||
|
||||
# A helper method, not necessary if not used internally.
|
||||
def installed?
|
||||
File.directory?(install_path)
|
||||
end
|
||||
|
||||
# The full path where the plugin should cache the gem so that it can be
|
||||
# installed latter.
|
||||
#
|
||||
# Note: Do not override if you don't know what you are doing.
|
||||
def app_cache_path(custom_path = nil)
|
||||
@app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
|
||||
end
|
||||
|
||||
# Used by definition.
|
||||
#
|
||||
# Note: Do not override if you don't know what you are doing.
|
||||
def unmet_deps
|
||||
specs.unmet_dependency_names
|
||||
end
|
||||
|
||||
# Note: Do not override if you don't know what you are doing.
|
||||
def can_lock?(spec)
|
||||
spec.source == self
|
||||
end
|
||||
|
||||
# Generates the content to be entered into the lockfile.
|
||||
# Saves type and remote and also calls to `options_to_lock`.
|
||||
#
|
||||
# Plugin should use `options_to_lock` to save information in lockfile
|
||||
# and not override this.
|
||||
#
|
||||
# Note: Do not override if you don't know what you are doing.
|
||||
def to_lock
|
||||
out = String.new("#{LockfileParser::PLUGIN}\n")
|
||||
out << " remote: #{@uri}\n"
|
||||
out << " type: #{@type}\n"
|
||||
options_to_lock.each do |opt, value|
|
||||
out << " #{opt}: #{value}\n"
|
||||
end
|
||||
out << " specs:\n"
|
||||
end
|
||||
|
||||
def to_s
|
||||
"plugin source for #{options[:type]} with uri #{uri}"
|
||||
end
|
||||
|
||||
# Note: Do not override if you don't know what you are doing.
|
||||
def include?(other)
|
||||
other == self
|
||||
end
|
||||
|
||||
def uri_hash
|
||||
SharedHelpers.digest(:SHA1).hexdigest(uri)
|
||||
end
|
||||
|
||||
# Note: Do not override if you don't know what you are doing.
|
||||
def gem_install_dir
|
||||
Bundler.install_path
|
||||
end
|
||||
|
||||
# It is used to obtain the full_gem_path.
|
||||
#
|
||||
# spec's loaded_from path is expanded against this to get full_gem_path
|
||||
#
|
||||
# Note: Do not override if you don't know what you are doing.
|
||||
def root
|
||||
Bundler.root
|
||||
end
|
||||
|
||||
# @private
|
||||
# Returns true
|
||||
def bundler_plugin_api_source?
|
||||
true
|
||||
end
|
||||
|
||||
# @private
|
||||
# This API on source might not be stable, and for now we expect plugins
|
||||
# to download all specs in `#specs`, so we implement the method for
|
||||
# compatibility purposes and leave it undocumented (and don't support)
|
||||
# overriding it)
|
||||
def double_check_for(*); end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module Plugin
|
||||
# Dsl to parse the Gemfile looking for plugins to install
|
||||
class DSL < Bundler::Dsl
|
||||
class PluginGemfileError < PluginError; end
|
||||
alias_method :_gem, :gem # To use for plugin installation as gem
|
||||
|
||||
# So that we don't have to override all there methods to dummy ones
|
||||
# explicitly.
|
||||
# They will be handled by method_missing
|
||||
[:gemspec, :gem, :path, :install_if, :platforms, :env].each {|m| undef_method m }
|
||||
|
||||
# This lists the plugins that was added automatically and not specified by
|
||||
# the user.
|
||||
#
|
||||
# When we encounter :type attribute with a source block, we add a plugin
|
||||
# by name bundler-source-<type> to list of plugins to be installed.
|
||||
#
|
||||
# These plugins are optional and are not installed when there is conflict
|
||||
# with any other plugin.
|
||||
attr_reader :inferred_plugins
|
||||
|
||||
def initialize
|
||||
super
|
||||
@sources = Plugin::SourceList.new
|
||||
@inferred_plugins = [] # The source plugins inferred from :type
|
||||
end
|
||||
|
||||
def plugin(name, *args)
|
||||
_gem(name, *args)
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
raise PluginGemfileError, "Undefined local variable or method `#{name}' for Gemfile" unless Bundler::Dsl.method_defined? name
|
||||
end
|
||||
|
||||
def source(source, *args, &blk)
|
||||
options = args.last.is_a?(Hash) ? args.pop.dup : {}
|
||||
options = normalize_hash(options)
|
||||
return super unless options.key?("type")
|
||||
|
||||
plugin_name = "bundler-source-#{options["type"]}"
|
||||
|
||||
return if @dependencies.any? {|d| d.name == plugin_name }
|
||||
|
||||
plugin(plugin_name)
|
||||
@inferred_plugins << plugin_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module Plugin
|
||||
module Events
|
||||
def self.define(const, event)
|
||||
const = const.to_sym.freeze
|
||||
if const_defined?(const) && const_get(const) != event
|
||||
raise ArgumentError, "Attempting to reassign #{const} to a different value"
|
||||
end
|
||||
const_set(const, event) unless const_defined?(const)
|
||||
@events ||= {}
|
||||
@events[event] = const
|
||||
end
|
||||
private_class_method :define
|
||||
|
||||
def self.reset
|
||||
@events.each_value do |const|
|
||||
remove_const(const)
|
||||
end
|
||||
@events = nil
|
||||
end
|
||||
private_class_method :reset
|
||||
|
||||
# Check if an event has been defined
|
||||
# @param event [String] An event to check
|
||||
# @return [Boolean] A boolean indicating if the event has been defined
|
||||
def self.defined_event?(event)
|
||||
@events ||= {}
|
||||
@events.key?(event)
|
||||
end
|
||||
|
||||
# @!parse
|
||||
# A hook called before each individual gem is installed
|
||||
# Includes a Bundler::ParallelInstaller::SpecInstallation.
|
||||
# No state, error, post_install_message will be present as nothing has installed yet
|
||||
# GEM_BEFORE_INSTALL = "before-install"
|
||||
define :GEM_BEFORE_INSTALL, "before-install"
|
||||
|
||||
# @!parse
|
||||
# A hook called after each individual gem is installed
|
||||
# Includes a Bundler::ParallelInstaller::SpecInstallation.
|
||||
# - If state is failed, an error will be present.
|
||||
# - If state is success, a post_install_message may be present.
|
||||
# GEM_AFTER_INSTALL = "after-install"
|
||||
define :GEM_AFTER_INSTALL, "after-install"
|
||||
|
||||
# @!parse
|
||||
# A hook called before any gems install
|
||||
# Includes an Array of Bundler::Dependency objects
|
||||
# GEM_BEFORE_INSTALL_ALL = "before-install-all"
|
||||
define :GEM_BEFORE_INSTALL_ALL, "before-install-all"
|
||||
|
||||
# @!parse
|
||||
# A hook called after any gems install
|
||||
# Includes an Array of Bundler::Dependency objects
|
||||
# GEM_AFTER_INSTALL_ALL = "after-install-all"
|
||||
define :GEM_AFTER_INSTALL_ALL, "after-install-all"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,162 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# Manages which plugins are installed and their sources. This also is supposed to map
|
||||
# which plugin does what (currently the features are not implemented so this class is
|
||||
# now a stub class).
|
||||
module Plugin
|
||||
class Index
|
||||
class CommandConflict < PluginError
|
||||
def initialize(plugin, commands)
|
||||
msg = "Command(s) `#{commands.join("`, `")}` declared by #{plugin} are already registered."
|
||||
super msg
|
||||
end
|
||||
end
|
||||
|
||||
class SourceConflict < PluginError
|
||||
def initialize(plugin, sources)
|
||||
msg = "Source(s) `#{sources.join("`, `")}` declared by #{plugin} are already registered."
|
||||
super msg
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :commands
|
||||
|
||||
def initialize
|
||||
@plugin_paths = {}
|
||||
@commands = {}
|
||||
@sources = {}
|
||||
@hooks = {}
|
||||
@load_paths = {}
|
||||
|
||||
begin
|
||||
load_index(global_index_file, true)
|
||||
rescue GenericSystemCallError
|
||||
# no need to fail when on a read-only FS, for example
|
||||
nil
|
||||
end
|
||||
load_index(local_index_file) if SharedHelpers.in_bundle?
|
||||
end
|
||||
|
||||
# This function is to be called when a new plugin is installed. This
|
||||
# function shall add the functions of the plugin to existing maps and also
|
||||
# the name to source location.
|
||||
#
|
||||
# @param [String] name of the plugin to be registered
|
||||
# @param [String] path where the plugin is installed
|
||||
# @param [Array<String>] load_paths for the plugin
|
||||
# @param [Array<String>] commands that are handled by the plugin
|
||||
# @param [Array<String>] sources that are handled by the plugin
|
||||
def register_plugin(name, path, load_paths, commands, sources, hooks)
|
||||
old_commands = @commands.dup
|
||||
|
||||
common = commands & @commands.keys
|
||||
raise CommandConflict.new(name, common) unless common.empty?
|
||||
commands.each {|c| @commands[c] = name }
|
||||
|
||||
common = sources & @sources.keys
|
||||
raise SourceConflict.new(name, common) unless common.empty?
|
||||
sources.each {|k| @sources[k] = name }
|
||||
|
||||
hooks.each {|e| (@hooks[e] ||= []) << name }
|
||||
|
||||
@plugin_paths[name] = path
|
||||
@load_paths[name] = load_paths
|
||||
save_index
|
||||
rescue StandardError
|
||||
@commands = old_commands
|
||||
raise
|
||||
end
|
||||
|
||||
# Path of default index file
|
||||
def index_file
|
||||
Plugin.root.join("index")
|
||||
end
|
||||
|
||||
# Path where the global index file is stored
|
||||
def global_index_file
|
||||
Plugin.global_root.join("index")
|
||||
end
|
||||
|
||||
# Path where the local index file is stored
|
||||
def local_index_file
|
||||
Plugin.local_root.join("index")
|
||||
end
|
||||
|
||||
def plugin_path(name)
|
||||
Pathname.new @plugin_paths[name]
|
||||
end
|
||||
|
||||
def load_paths(name)
|
||||
@load_paths[name]
|
||||
end
|
||||
|
||||
# Fetch the name of plugin handling the command
|
||||
def command_plugin(command)
|
||||
@commands[command]
|
||||
end
|
||||
|
||||
def installed?(name)
|
||||
@plugin_paths[name]
|
||||
end
|
||||
|
||||
def source?(source)
|
||||
@sources.key? source
|
||||
end
|
||||
|
||||
def source_plugin(name)
|
||||
@sources[name]
|
||||
end
|
||||
|
||||
# Returns the list of plugin names handling the passed event
|
||||
def hook_plugins(event)
|
||||
@hooks[event] || []
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Reads the index file from the directory and initializes the instance
|
||||
# variables.
|
||||
#
|
||||
# It skips the sources if the second param is true
|
||||
# @param [Pathname] index file path
|
||||
# @param [Boolean] is the index file global index
|
||||
def load_index(index_file, global = false)
|
||||
SharedHelpers.filesystem_access(index_file, :read) do |index_f|
|
||||
valid_file = index_f && index_f.exist? && !index_f.size.zero?
|
||||
break unless valid_file
|
||||
|
||||
data = index_f.read
|
||||
|
||||
require "bundler/yaml_serializer"
|
||||
index = YAMLSerializer.load(data)
|
||||
|
||||
@commands.merge!(index["commands"])
|
||||
@hooks.merge!(index["hooks"])
|
||||
@load_paths.merge!(index["load_paths"])
|
||||
@plugin_paths.merge!(index["plugin_paths"])
|
||||
@sources.merge!(index["sources"]) unless global
|
||||
end
|
||||
end
|
||||
|
||||
# Should be called when any of the instance variables change. Stores the
|
||||
# instance variables in YAML format. (The instance variables are supposed
|
||||
# to be only String key value pairs)
|
||||
def save_index
|
||||
index = {
|
||||
"commands" => @commands,
|
||||
"hooks" => @hooks,
|
||||
"load_paths" => @load_paths,
|
||||
"plugin_paths" => @plugin_paths,
|
||||
"sources" => @sources,
|
||||
}
|
||||
|
||||
require "bundler/yaml_serializer"
|
||||
SharedHelpers.filesystem_access(index_file) do |index_f|
|
||||
FileUtils.mkdir_p(index_f.dirname)
|
||||
File.open(index_f, "w") {|f| f.puts YAMLSerializer.dump(index) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# Handles the installation of plugin in appropriate directories.
|
||||
#
|
||||
# This class is supposed to be wrapper over the existing gem installation infra
|
||||
# but currently it itself handles everything as the Source's subclasses (e.g. Source::RubyGems)
|
||||
# are heavily dependent on the Gemfile.
|
||||
module Plugin
|
||||
class Installer
|
||||
autoload :Rubygems, "bundler/plugin/installer/rubygems"
|
||||
autoload :Git, "bundler/plugin/installer/git"
|
||||
|
||||
def install(names, options)
|
||||
version = options[:version] || [">= 0"]
|
||||
Bundler.settings.temporary(:lockfile_uses_separate_rubygems_sources => false, :disable_multisource => false) do
|
||||
if options[:git]
|
||||
install_git(names, version, options)
|
||||
else
|
||||
sources = options[:source] || Bundler.rubygems.sources
|
||||
install_rubygems(names, version, sources)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Installs the plugin from Definition object created by limited parsing of
|
||||
# Gemfile searching for plugins to be installed
|
||||
#
|
||||
# @param [Definition] definition object
|
||||
# @return [Hash] map of names to their specs they are installed with
|
||||
def install_definition(definition)
|
||||
def definition.lock(*); end
|
||||
definition.resolve_remotely!
|
||||
specs = definition.specs
|
||||
|
||||
install_from_specs specs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def install_git(names, version, options)
|
||||
uri = options.delete(:git)
|
||||
options["uri"] = uri
|
||||
|
||||
source_list = SourceList.new
|
||||
source_list.add_git_source(options)
|
||||
|
||||
# To support both sources
|
||||
if options[:source]
|
||||
source_list.add_rubygems_source("remotes" => options[:source])
|
||||
end
|
||||
|
||||
deps = names.map {|name| Dependency.new name, version }
|
||||
|
||||
definition = Definition.new(nil, deps, source_list, true)
|
||||
install_definition(definition)
|
||||
end
|
||||
|
||||
# Installs the plugin from rubygems source and returns the path where the
|
||||
# plugin was installed
|
||||
#
|
||||
# @param [String] name of the plugin gem to search in the source
|
||||
# @param [Array] version of the gem to install
|
||||
# @param [String, Array<String>] source(s) to resolve the gem
|
||||
#
|
||||
# @return [Hash] map of names to the specs of plugins installed
|
||||
def install_rubygems(names, version, sources)
|
||||
deps = names.map {|name| Dependency.new name, version }
|
||||
|
||||
source_list = SourceList.new
|
||||
source_list.add_rubygems_source("remotes" => sources)
|
||||
|
||||
definition = Definition.new(nil, deps, source_list, true)
|
||||
install_definition(definition)
|
||||
end
|
||||
|
||||
# Installs the plugins and deps from the provided specs and returns map of
|
||||
# gems to their paths
|
||||
#
|
||||
# @param specs to install
|
||||
#
|
||||
# @return [Hash] map of names to the specs
|
||||
def install_from_specs(specs)
|
||||
paths = {}
|
||||
|
||||
specs.each do |spec|
|
||||
spec.source.install spec
|
||||
|
||||
paths[spec.name] = spec
|
||||
end
|
||||
|
||||
paths
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module Plugin
|
||||
class Installer
|
||||
class Git < Bundler::Source::Git
|
||||
def cache_path
|
||||
@cache_path ||= begin
|
||||
git_scope = "#{base_name}-#{uri_hash}"
|
||||
|
||||
Plugin.cache.join("bundler", "git", git_scope)
|
||||
end
|
||||
end
|
||||
|
||||
def install_path
|
||||
@install_path ||= begin
|
||||
git_scope = "#{base_name}-#{shortref_for_path(revision)}"
|
||||
|
||||
Plugin.root.join("bundler", "gems", git_scope)
|
||||
end
|
||||
end
|
||||
|
||||
def version_message(spec)
|
||||
"#{spec.name} #{spec.version}"
|
||||
end
|
||||
|
||||
def root
|
||||
Plugin.root
|
||||
end
|
||||
|
||||
def generate_bin(spec, disable_extensions = false)
|
||||
# Need to find a way without code duplication
|
||||
# For now, we can ignore this
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module Plugin
|
||||
class Installer
|
||||
class Rubygems < Bundler::Source::Rubygems
|
||||
def version_message(spec)
|
||||
"#{spec.name} #{spec.version}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def requires_sudo?
|
||||
false # Will change on implementation of project level plugins
|
||||
end
|
||||
|
||||
def rubygems_dir
|
||||
Plugin.root
|
||||
end
|
||||
|
||||
def cache_path
|
||||
Plugin.cache
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# SourceList object to be used while parsing the Gemfile, setting the
|
||||
# approptiate options to be used with Source classes for plugin installation
|
||||
module Plugin
|
||||
class SourceList < Bundler::SourceList
|
||||
def add_git_source(options = {})
|
||||
add_source_to_list Plugin::Installer::Git.new(options), git_sources
|
||||
end
|
||||
|
||||
def add_rubygems_source(options = {})
|
||||
add_source_to_list Plugin::Installer::Rubygems.new(options), @rubygems_sources
|
||||
end
|
||||
|
||||
def all_sources
|
||||
path_sources + git_sources + rubygems_sources + [metadata_source]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rubygems_aggregate_class
|
||||
Plugin::Installer::Rubygems
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class ProcessLock
|
||||
def self.lock(bundle_path = Bundler.bundle_path)
|
||||
lock_file_path = File.join(bundle_path, "bundler.lock")
|
||||
has_lock = false
|
||||
|
||||
File.open(lock_file_path, "w") do |f|
|
||||
f.flock(File::LOCK_EX)
|
||||
has_lock = true
|
||||
yield
|
||||
f.flock(File::LOCK_UN)
|
||||
end
|
||||
rescue Errno::EACCES, Errno::ENOLCK, *[SharedHelpers.const_get_safely(:ENOTSUP, Errno)].compact
|
||||
# In the case the user does not have access to
|
||||
# create the lock file or is using NFS where
|
||||
# locks are not available we skip locking.
|
||||
yield
|
||||
ensure
|
||||
FileUtils.rm_f(lock_file_path) if has_lock
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Psych could be a gem, so try to ask for it
|
||||
begin
|
||||
gem "psych"
|
||||
rescue LoadError
|
||||
end if defined?(gem)
|
||||
|
||||
# Psych could be in the stdlib
|
||||
# but it's too late if Syck is already loaded
|
||||
begin
|
||||
require "psych" unless defined?(Syck)
|
||||
rescue LoadError
|
||||
# Apparently Psych wasn't available. Oh well.
|
||||
end
|
||||
|
||||
# At least load the YAML stdlib, whatever that may be
|
||||
require "yaml" unless defined?(YAML.dump)
|
||||
|
||||
module Bundler
|
||||
# On encountering invalid YAML,
|
||||
# Psych raises Psych::SyntaxError
|
||||
if defined?(::Psych::SyntaxError)
|
||||
YamlLibrarySyntaxError = ::Psych::SyntaxError
|
||||
else # Syck raises ArgumentError
|
||||
YamlLibrarySyntaxError = ::ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
require "bundler/deprecate"
|
||||
begin
|
||||
Bundler::Deprecate.skip_during do
|
||||
require "rubygems/safe_yaml"
|
||||
end
|
||||
rescue LoadError
|
||||
# it's OK if the file isn't there
|
||||
end
|
|
@ -0,0 +1,114 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "uri"
|
||||
|
||||
module Bundler
|
||||
# Represents a lazily loaded gem specification, where the full specification
|
||||
# is on the source server in rubygems' "quick" index. The proxy object is to
|
||||
# be seeded with what we're given from the source's abbreviated index - the
|
||||
# full specification will only be fetched when necessary.
|
||||
class RemoteSpecification
|
||||
include MatchPlatform
|
||||
include Comparable
|
||||
|
||||
attr_reader :name, :version, :platform
|
||||
attr_writer :dependencies
|
||||
attr_accessor :source, :remote
|
||||
|
||||
def initialize(name, version, platform, spec_fetcher)
|
||||
@name = name
|
||||
@version = Gem::Version.create version
|
||||
@platform = platform
|
||||
@spec_fetcher = spec_fetcher
|
||||
@dependencies = nil
|
||||
end
|
||||
|
||||
# Needed before installs, since the arch matters then and quick
|
||||
# specs don't bother to include the arch in the platform string
|
||||
def fetch_platform
|
||||
@platform = _remote_specification.platform
|
||||
end
|
||||
|
||||
def full_name
|
||||
if platform == Gem::Platform::RUBY || platform.nil?
|
||||
"#{@name}-#{@version}"
|
||||
else
|
||||
"#{@name}-#{@version}-#{platform}"
|
||||
end
|
||||
end
|
||||
|
||||
# Compare this specification against another object. Using sort_obj
|
||||
# is compatible with Gem::Specification and other Bundler or RubyGems
|
||||
# objects. Otherwise, use the default Object comparison.
|
||||
def <=>(other)
|
||||
if other.respond_to?(:sort_obj)
|
||||
sort_obj <=> other.sort_obj
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Because Rubyforge cannot be trusted to provide valid specifications
|
||||
# once the remote gem is downloaded, the backend specification will
|
||||
# be swapped out.
|
||||
def __swap__(spec)
|
||||
SharedHelpers.ensure_same_dependencies(self, dependencies, spec.dependencies)
|
||||
@_remote_specification = spec
|
||||
end
|
||||
|
||||
# Create a delegate used for sorting. This strategy is copied from
|
||||
# RubyGems 2.23 and ensures that Bundler's specifications can be
|
||||
# compared and sorted with RubyGems' own specifications.
|
||||
#
|
||||
# @see #<=>
|
||||
# @see Gem::Specification#sort_obj
|
||||
#
|
||||
# @return [Array] an object you can use to compare and sort this
|
||||
# specification against other specifications
|
||||
def sort_obj
|
||||
[@name, @version, @platform == Gem::Platform::RUBY ? -1 : 1]
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#<#{self.class} name=#{name} version=#{version} platform=#{platform}>"
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@dependencies ||= begin
|
||||
deps = method_missing(:dependencies)
|
||||
|
||||
# allow us to handle when the specs dependencies are an array of array of string
|
||||
# see https://github.com/bundler/bundler/issues/5797
|
||||
deps = deps.map {|d| d.is_a?(Gem::Dependency) ? d : Gem::Dependency.new(*d) }
|
||||
|
||||
deps
|
||||
end
|
||||
end
|
||||
|
||||
def git_version
|
||||
return unless loaded_from && source.is_a?(Bundler::Source::Git)
|
||||
" #{source.revision[0..6]}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_ary
|
||||
nil
|
||||
end
|
||||
|
||||
def _remote_specification
|
||||
@_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, @platform])
|
||||
@_remote_specification || raise(GemspecError, "Gemspec data for #{full_name} was" \
|
||||
" missing from the server! Try installing with `--full-index` as a workaround.")
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &blk)
|
||||
_remote_specification.send(method, *args, &blk)
|
||||
end
|
||||
|
||||
def respond_to?(method, include_all = false)
|
||||
super || _remote_specification.respond_to?(method, include_all)
|
||||
end
|
||||
public :respond_to?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,373 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Resolver
|
||||
require "bundler/vendored_molinillo"
|
||||
require "bundler/resolver/spec_group"
|
||||
|
||||
# Figures out the best possible configuration of gems that satisfies
|
||||
# the list of passed dependencies and any child dependencies without
|
||||
# causing any gem activation errors.
|
||||
#
|
||||
# ==== Parameters
|
||||
# *dependencies<Gem::Dependency>:: The list of dependencies to resolve
|
||||
#
|
||||
# ==== Returns
|
||||
# <GemBundle>,nil:: If the list of dependencies can be resolved, a
|
||||
# collection of gemspecs is returned. Otherwise, nil is returned.
|
||||
def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil)
|
||||
platforms = Set.new(platforms) if platforms
|
||||
base = SpecSet.new(base) unless base.is_a?(SpecSet)
|
||||
resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
|
||||
result = resolver.start(requirements)
|
||||
SpecSet.new(result)
|
||||
end
|
||||
|
||||
def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
|
||||
@index = index
|
||||
@source_requirements = source_requirements
|
||||
@base = base
|
||||
@resolver = Molinillo::Resolver.new(self, self)
|
||||
@search_for = {}
|
||||
@base_dg = Molinillo::DependencyGraph.new
|
||||
@base.each do |ls|
|
||||
dep = Dependency.new(ls.name, ls.version)
|
||||
@base_dg.add_vertex(ls.name, DepProxy.new(dep, ls.platform), true)
|
||||
end
|
||||
additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) }
|
||||
@platforms = platforms
|
||||
@gem_version_promoter = gem_version_promoter
|
||||
@allow_bundler_dependency_conflicts = Bundler.feature_flag.allow_bundler_dependency_conflicts?
|
||||
@lockfile_uses_separate_rubygems_sources = Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
|
||||
@use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major?
|
||||
end
|
||||
|
||||
def start(requirements)
|
||||
@gem_version_promoter.prerelease_specified = @prerelease_specified = {}
|
||||
requirements.each {|dep| @prerelease_specified[dep.name] ||= dep.prerelease? }
|
||||
|
||||
verify_gemfile_dependencies_are_found!(requirements)
|
||||
dg = @resolver.resolve(requirements, @base_dg)
|
||||
dg.map(&:payload).
|
||||
reject {|sg| sg.name.end_with?("\0") }.
|
||||
map(&:to_specs).flatten
|
||||
rescue Molinillo::VersionConflict => e
|
||||
message = version_conflict_message(e)
|
||||
raise VersionConflict.new(e.conflicts.keys.uniq, message)
|
||||
rescue Molinillo::CircularDependencyError => e
|
||||
names = e.dependencies.sort_by(&:name).map {|d| "gem '#{d.name}'" }
|
||||
raise CyclicDependencyError, "Your bundle requires gems that depend" \
|
||||
" on each other, creating an infinite loop. Please remove" \
|
||||
" #{names.count > 1 ? "either " : ""}#{names.join(" or ")}" \
|
||||
" and try again."
|
||||
end
|
||||
|
||||
include Molinillo::UI
|
||||
|
||||
# Conveys debug information to the user.
|
||||
#
|
||||
# @param [Integer] depth the current depth of the resolution process.
|
||||
# @return [void]
|
||||
def debug(depth = 0)
|
||||
return unless debug?
|
||||
debug_info = yield
|
||||
debug_info = debug_info.inspect unless debug_info.is_a?(String)
|
||||
STDERR.puts debug_info.split("\n").map {|s| " " * depth + s }
|
||||
end
|
||||
|
||||
def debug?
|
||||
return @debug_mode if defined?(@debug_mode)
|
||||
@debug_mode = ENV["DEBUG_RESOLVER"] || ENV["DEBUG_RESOLVER_TREE"] || false
|
||||
end
|
||||
|
||||
def before_resolution
|
||||
Bundler.ui.info "Resolving dependencies...", debug?
|
||||
end
|
||||
|
||||
def after_resolution
|
||||
Bundler.ui.info ""
|
||||
end
|
||||
|
||||
def indicate_progress
|
||||
Bundler.ui.info ".", false unless debug?
|
||||
end
|
||||
|
||||
include Molinillo::SpecificationProvider
|
||||
|
||||
def dependencies_for(specification)
|
||||
specification.dependencies_for_activated_platforms
|
||||
end
|
||||
|
||||
def search_for(dependency)
|
||||
platform = dependency.__platform
|
||||
dependency = dependency.dep unless dependency.is_a? Gem::Dependency
|
||||
search = @search_for[dependency] ||= begin
|
||||
index = index_for(dependency)
|
||||
results = index.search(dependency, @base[dependency.name])
|
||||
|
||||
if vertex = @base_dg.vertex_named(dependency.name)
|
||||
locked_requirement = vertex.payload.requirement
|
||||
end
|
||||
|
||||
if !@prerelease_specified[dependency.name] && (!@use_gvp || locked_requirement.nil?)
|
||||
# Move prereleases to the beginning of the list, so they're considered
|
||||
# last during resolution.
|
||||
pre, results = results.partition {|spec| spec.version.prerelease? }
|
||||
results = pre + results
|
||||
end
|
||||
|
||||
spec_groups = if results.any?
|
||||
nested = []
|
||||
results.each do |spec|
|
||||
version, specs = nested.last
|
||||
if version == spec.version
|
||||
specs << spec
|
||||
else
|
||||
nested << [spec.version, [spec]]
|
||||
end
|
||||
end
|
||||
nested.reduce([]) do |groups, (version, specs)|
|
||||
next groups if locked_requirement && !locked_requirement.satisfied_by?(version)
|
||||
spec_group = SpecGroup.new(specs)
|
||||
spec_group.ignores_bundler_dependencies = @allow_bundler_dependency_conflicts
|
||||
groups << spec_group
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
# GVP handles major itself, but it's still a bit risky to trust it with it
|
||||
# until we get it settled with new behavior. For 2.x it can take over all cases.
|
||||
if !@use_gvp
|
||||
spec_groups
|
||||
else
|
||||
@gem_version_promoter.sort_versions(dependency, spec_groups)
|
||||
end
|
||||
end
|
||||
search.select {|sg| sg.for?(platform) }.each {|sg| sg.activate_platform!(platform) }
|
||||
end
|
||||
|
||||
def index_for(dependency)
|
||||
source = @source_requirements[dependency.name]
|
||||
if source
|
||||
source.specs
|
||||
elsif @lockfile_uses_separate_rubygems_sources
|
||||
Index.build do |idx|
|
||||
if dependency.all_sources
|
||||
dependency.all_sources.each {|s| idx.add_source(s.specs) if s }
|
||||
else
|
||||
idx.add_source @source_requirements[:default].specs
|
||||
end
|
||||
end
|
||||
else
|
||||
@index
|
||||
end
|
||||
end
|
||||
|
||||
def name_for(dependency)
|
||||
dependency.name
|
||||
end
|
||||
|
||||
def name_for_explicit_dependency_source
|
||||
Bundler.default_gemfile.basename.to_s
|
||||
rescue
|
||||
"Gemfile"
|
||||
end
|
||||
|
||||
def name_for_locking_dependency_source
|
||||
Bundler.default_lockfile.basename.to_s
|
||||
rescue
|
||||
"Gemfile.lock"
|
||||
end
|
||||
|
||||
def requirement_satisfied_by?(requirement, activated, spec)
|
||||
return false unless requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec)
|
||||
spec.activate_platform!(requirement.__platform) if !@platforms || @platforms.include?(requirement.__platform)
|
||||
true
|
||||
end
|
||||
|
||||
def relevant_sources_for_vertex(vertex)
|
||||
if vertex.root?
|
||||
[@source_requirements[vertex.name]]
|
||||
elsif @lockfile_uses_separate_rubygems_sources
|
||||
vertex.recursive_predecessors.map do |v|
|
||||
@source_requirements[v.name]
|
||||
end << @source_requirements[:default]
|
||||
end
|
||||
end
|
||||
|
||||
def sort_dependencies(dependencies, activated, conflicts)
|
||||
dependencies.sort_by do |dependency|
|
||||
dependency.all_sources = relevant_sources_for_vertex(activated.vertex_named(dependency.name))
|
||||
name = name_for(dependency)
|
||||
vertex = activated.vertex_named(name)
|
||||
[
|
||||
@base_dg.vertex_named(name) ? 0 : 1,
|
||||
vertex.payload ? 0 : 1,
|
||||
vertex.root? ? 0 : 1,
|
||||
amount_constrained(dependency),
|
||||
conflicts[name] ? 0 : 1,
|
||||
vertex.payload ? 0 : search_for(dependency).count,
|
||||
self.class.platform_sort_key(dependency.__platform),
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
# Sort platforms from most general to most specific
|
||||
def self.sort_platforms(platforms)
|
||||
platforms.sort_by do |platform|
|
||||
platform_sort_key(platform)
|
||||
end
|
||||
end
|
||||
|
||||
def self.platform_sort_key(platform)
|
||||
return ["", "", ""] if Gem::Platform::RUBY == platform
|
||||
platform.to_a.map {|part| part || "" }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# returns an integer \in (-\infty, 0]
|
||||
# a number closer to 0 means the dependency is less constraining
|
||||
#
|
||||
# dependencies w/ 0 or 1 possibilities (ignoring version requirements)
|
||||
# are given very negative values, so they _always_ sort first,
|
||||
# before dependencies that are unconstrained
|
||||
def amount_constrained(dependency)
|
||||
@amount_constrained ||= {}
|
||||
@amount_constrained[dependency.name] ||= begin
|
||||
if (base = @base[dependency.name]) && !base.empty?
|
||||
dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1
|
||||
else
|
||||
all = index_for(dependency).search(dependency.name).size
|
||||
|
||||
if all <= 1
|
||||
all - 1_000_000
|
||||
else
|
||||
search = search_for(dependency)
|
||||
search = @prerelease_specified[dependency.name] ? search.count : search.count {|s| !s.version.prerelease? }
|
||||
search - all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def verify_gemfile_dependencies_are_found!(requirements)
|
||||
requirements.each do |requirement|
|
||||
name = requirement.name
|
||||
next if name == "bundler"
|
||||
next unless search_for(requirement).empty?
|
||||
|
||||
cache_message = begin
|
||||
" or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist?
|
||||
rescue GemfileNotFound
|
||||
nil
|
||||
end
|
||||
|
||||
if (base = @base[name]) && !base.empty?
|
||||
version = base.first.version
|
||||
message = "You have requested:\n" \
|
||||
" #{name} #{requirement.requirement}\n\n" \
|
||||
"The bundle currently has #{name} locked at #{version}.\n" \
|
||||
"Try running `bundle update #{name}`\n\n" \
|
||||
"If you are updating multiple gems in your Gemfile at once,\n" \
|
||||
"try passing them all to `bundle update`"
|
||||
elsif source = @source_requirements[name]
|
||||
specs = source.specs[name]
|
||||
versions_with_platforms = specs.map {|s| [s.version, s.platform] }
|
||||
message = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source}#{cache_message}.\n")
|
||||
message << if versions_with_platforms.any?
|
||||
"The source contains '#{name}' at: #{formatted_versions_with_platforms(versions_with_platforms)}"
|
||||
else
|
||||
"The source does not contain any versions of '#{name}'"
|
||||
end
|
||||
else
|
||||
message = "Could not find gem '#{requirement}' in any of the gem sources " \
|
||||
"listed in your Gemfile#{cache_message}."
|
||||
end
|
||||
raise GemNotFound, message
|
||||
end
|
||||
end
|
||||
|
||||
def formatted_versions_with_platforms(versions_with_platforms)
|
||||
version_platform_strs = versions_with_platforms.map do |vwp|
|
||||
version = vwp.first
|
||||
platform = vwp.last
|
||||
version_platform_str = String.new(version.to_s)
|
||||
version_platform_str << " #{platform}" unless platform.nil? || platform == Gem::Platform::RUBY
|
||||
version_platform_str
|
||||
end
|
||||
version_platform_strs.join(", ")
|
||||
end
|
||||
|
||||
def version_conflict_message(e)
|
||||
e.message_with_trees(
|
||||
:solver_name => "Bundler",
|
||||
:possibility_type => "gem",
|
||||
:reduce_trees => lambda do |trees|
|
||||
# called first, because we want to reduce the amount of work required to find maximal empty sets
|
||||
trees = trees.uniq {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } }
|
||||
|
||||
# bail out if tree size is too big for Array#combination to make any sense
|
||||
return trees if trees.size > 15
|
||||
maximal = 1.upto(trees.size).map do |size|
|
||||
trees.map(&:last).flatten(1).combination(size).to_a
|
||||
end.flatten(1).select do |deps|
|
||||
Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement)))
|
||||
end.min_by(&:size)
|
||||
trees.reject! {|t| !maximal.include?(t.last) } if maximal
|
||||
|
||||
trees = trees.sort_by {|t| t.flatten.map(&:to_s) }
|
||||
trees.uniq! {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } }
|
||||
|
||||
trees.sort_by {|t| t.reverse.map(&:name) }
|
||||
end,
|
||||
:printable_requirement => lambda {|req| SharedHelpers.pretty_dependency(req) },
|
||||
:additional_message_for_conflict => lambda do |o, name, conflict|
|
||||
if name == "bundler"
|
||||
o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION}))
|
||||
other_bundler_required = !conflict.requirement.requirement.satisfied_by?(Gem::Version.new Bundler::VERSION)
|
||||
end
|
||||
|
||||
if name == "bundler" && other_bundler_required
|
||||
o << "\n"
|
||||
o << "This Gemfile requires a different version of Bundler.\n"
|
||||
o << "Perhaps you need to update Bundler by running `gem install bundler`?\n"
|
||||
end
|
||||
if conflict.locked_requirement
|
||||
o << "\n"
|
||||
o << %(Running `bundle update` will rebuild your snapshot from scratch, using only\n)
|
||||
o << %(the gems in your Gemfile, which may resolve the conflict.\n)
|
||||
elsif !conflict.existing
|
||||
o << "\n"
|
||||
|
||||
relevant_sources = if conflict.requirement.source
|
||||
[conflict.requirement.source]
|
||||
elsif conflict.requirement.all_sources
|
||||
conflict.requirement.all_sources
|
||||
elsif @lockfile_uses_separate_rubygems_sources
|
||||
# every conflict should have an explicit group of sources when we
|
||||
# enforce strict pinning
|
||||
raise "no source set for #{conflict}"
|
||||
else
|
||||
[]
|
||||
end.compact.map(&:to_s).uniq.sort
|
||||
|
||||
o << "Could not find gem '#{SharedHelpers.pretty_dependency(conflict.requirement)}'"
|
||||
if conflict.requirement_trees.first.size > 1
|
||||
o << ", which is required by "
|
||||
o << "gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}',"
|
||||
end
|
||||
o << " "
|
||||
|
||||
o << if relevant_sources.empty?
|
||||
"in any of the sources.\n"
|
||||
else
|
||||
"in any of the relevant sources:\n #{relevant_sources * "\n "}\n"
|
||||
end
|
||||
end
|
||||
end,
|
||||
:version_for_spec => lambda {|spec| spec.version }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,106 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Resolver
|
||||
class SpecGroup
|
||||
include GemHelpers
|
||||
|
||||
attr_accessor :name, :version, :source
|
||||
attr_accessor :ignores_bundler_dependencies
|
||||
|
||||
def initialize(all_specs)
|
||||
raise ArgumentError, "cannot initialize with an empty value" unless exemplary_spec = all_specs.first
|
||||
@name = exemplary_spec.name
|
||||
@version = exemplary_spec.version
|
||||
@source = exemplary_spec.source
|
||||
|
||||
@activated_platforms = []
|
||||
@dependencies = nil
|
||||
@specs = Hash.new do |specs, platform|
|
||||
specs[platform] = select_best_platform_match(all_specs, platform)
|
||||
end
|
||||
@ignores_bundler_dependencies = true
|
||||
end
|
||||
|
||||
def to_specs
|
||||
@activated_platforms.map do |p|
|
||||
next unless s = @specs[p]
|
||||
lazy_spec = LazySpecification.new(name, version, s.platform, source)
|
||||
lazy_spec.dependencies.replace s.dependencies
|
||||
lazy_spec
|
||||
end.compact
|
||||
end
|
||||
|
||||
def activate_platform!(platform)
|
||||
return unless for?(platform)
|
||||
return if @activated_platforms.include?(platform)
|
||||
@activated_platforms << platform
|
||||
end
|
||||
|
||||
def for?(platform)
|
||||
spec = @specs[platform]
|
||||
!spec.nil?
|
||||
end
|
||||
|
||||
def to_s
|
||||
@to_s ||= "#{name} (#{version})"
|
||||
end
|
||||
|
||||
def dependencies_for_activated_platforms
|
||||
dependencies = @activated_platforms.map {|p| __dependencies[p] }
|
||||
metadata_dependencies = @activated_platforms.map do |platform|
|
||||
metadata_dependencies(@specs[platform], platform)
|
||||
end
|
||||
dependencies.concat(metadata_dependencies).flatten
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
return unless other.is_a?(SpecGroup)
|
||||
name == other.name &&
|
||||
version == other.version &&
|
||||
source == other.source
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
name.eql?(other.name) &&
|
||||
version.eql?(other.version) &&
|
||||
source.eql?(other.source)
|
||||
end
|
||||
|
||||
def hash
|
||||
to_s.hash ^ source.hash
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def __dependencies
|
||||
@dependencies = Hash.new do |dependencies, platform|
|
||||
dependencies[platform] = []
|
||||
if spec = @specs[platform]
|
||||
spec.dependencies.each do |dep|
|
||||
next if dep.type == :development
|
||||
next if @ignores_bundler_dependencies && dep.name == "bundler".freeze
|
||||
dependencies[platform] << DepProxy.new(dep, platform)
|
||||
end
|
||||
end
|
||||
dependencies[platform]
|
||||
end
|
||||
end
|
||||
|
||||
def metadata_dependencies(spec, platform)
|
||||
return [] unless spec
|
||||
# Only allow endpoint specifications since they won't hit the network to
|
||||
# fetch the full gemspec when calling required_ruby_version
|
||||
return [] if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification)
|
||||
dependencies = []
|
||||
if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none?
|
||||
dependencies << DepProxy.new(Gem::Dependency.new("ruby\0", spec.required_ruby_version), platform)
|
||||
end
|
||||
if !spec.required_rubygems_version.nil? && !spec.required_rubygems_version.none?
|
||||
dependencies << DepProxy.new(Gem::Dependency.new("rubygems\0", spec.required_rubygems_version), platform)
|
||||
end
|
||||
dependencies
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# General purpose class for retrying code that may fail
|
||||
class Retry
|
||||
attr_accessor :name, :total_runs, :current_run
|
||||
|
||||
class << self
|
||||
def default_attempts
|
||||
default_retries + 1
|
||||
end
|
||||
alias_method :attempts, :default_attempts
|
||||
|
||||
def default_retries
|
||||
Bundler.settings[:retry]
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(name, exceptions = nil, retries = self.class.default_retries)
|
||||
@name = name
|
||||
@retries = retries
|
||||
@exceptions = Array(exceptions) || []
|
||||
@total_runs = @retries + 1 # will run once, then upto attempts.times
|
||||
end
|
||||
|
||||
def attempt(&block)
|
||||
@current_run = 0
|
||||
@failed = false
|
||||
@error = nil
|
||||
run(&block) while keep_trying?
|
||||
@result
|
||||
end
|
||||
alias_method :attempts, :attempt
|
||||
|
||||
private
|
||||
|
||||
def run(&block)
|
||||
@failed = false
|
||||
@current_run += 1
|
||||
@result = block.call
|
||||
rescue => e
|
||||
fail_attempt(e)
|
||||
end
|
||||
|
||||
def fail_attempt(e)
|
||||
@failed = true
|
||||
if last_attempt? || @exceptions.any? {|k| e.is_a?(k) }
|
||||
Bundler.ui.info "" unless Bundler.ui.debug?
|
||||
raise e
|
||||
end
|
||||
return true unless name
|
||||
Bundler.ui.info "" unless Bundler.ui.debug? # Add new line incase dots preceded this
|
||||
Bundler.ui.warn "Retrying #{name} due to error (#{current_run.next}/#{total_runs}): #{e.class} #{e.message}", Bundler.ui.debug?
|
||||
end
|
||||
|
||||
def keep_trying?
|
||||
return true if current_run.zero?
|
||||
return false if last_attempt?
|
||||
return true if @failed
|
||||
end
|
||||
|
||||
def last_attempt?
|
||||
current_run >= total_runs
|
||||
end
|
||||
end
|
||||
end
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче