This commit is contained in:
@@ -0,0 +1,701 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "bundler/rubygems_ext"
|
||||
require_relative "bundler/vendored_fileutils"
|
||||
autoload :Pathname, "pathname" unless defined?(Pathname)
|
||||
require "rbconfig"
|
||||
|
||||
require_relative "bundler/errors"
|
||||
require_relative "bundler/environment_preserver"
|
||||
require_relative "bundler/plugin"
|
||||
require_relative "bundler/rubygems_integration"
|
||||
require_relative "bundler/version"
|
||||
require_relative "bundler/current_ruby"
|
||||
require_relative "bundler/build_metadata"
|
||||
|
||||
# Bundler provides a consistent environment for Ruby projects by
|
||||
# tracking and installing the exact gems and versions that are needed.
|
||||
#
|
||||
# Bundler is a part of Ruby's standard library.
|
||||
#
|
||||
# Bundler is used by creating _gemfiles_ listing all the project dependencies
|
||||
# and (optionally) their versions and then using
|
||||
#
|
||||
# require 'bundler/setup'
|
||||
#
|
||||
# or Bundler.setup to setup environment where only specified gems and their
|
||||
# specified versions could be used.
|
||||
#
|
||||
# See {Bundler documentation}[https://guides.rubygems.org/command-reference/bundle/] for extensive documentation
|
||||
# on gemfiles creation and Bundler usage.
|
||||
#
|
||||
# As a standard library inside project, Bundler could be used for introspection
|
||||
# of loaded and required modules.
|
||||
#
|
||||
module Bundler
|
||||
environment_preserver = EnvironmentPreserver.from_env
|
||||
ORIGINAL_ENV = environment_preserver.restore
|
||||
environment_preserver.replace_with_backup
|
||||
SUDO_MUTEX = Thread::Mutex.new
|
||||
|
||||
autoload :Checksum, File.expand_path("bundler/checksum", __dir__)
|
||||
autoload :CLI, File.expand_path("bundler/cli", __dir__)
|
||||
autoload :CIDetector, File.expand_path("bundler/ci_detector", __dir__)
|
||||
autoload :CompactIndexClient, File.expand_path("bundler/compact_index_client", __dir__)
|
||||
autoload :Definition, File.expand_path("bundler/definition", __dir__)
|
||||
autoload :Dependency, File.expand_path("bundler/dependency", __dir__)
|
||||
autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__)
|
||||
autoload :Digest, File.expand_path("bundler/digest", __dir__)
|
||||
autoload :Dsl, File.expand_path("bundler/dsl", __dir__)
|
||||
autoload :EndpointSpecification, File.expand_path("bundler/endpoint_specification", __dir__)
|
||||
autoload :Env, File.expand_path("bundler/env", __dir__)
|
||||
autoload :Fetcher, File.expand_path("bundler/fetcher", __dir__)
|
||||
autoload :FeatureFlag, File.expand_path("bundler/feature_flag", __dir__)
|
||||
autoload :FREEBSD, File.expand_path("bundler/constants", __dir__)
|
||||
autoload :GemHelper, File.expand_path("bundler/gem_helper", __dir__)
|
||||
autoload :GemVersionPromoter, File.expand_path("bundler/gem_version_promoter", __dir__)
|
||||
autoload :Index, File.expand_path("bundler/index", __dir__)
|
||||
autoload :Injector, File.expand_path("bundler/injector", __dir__)
|
||||
autoload :Installer, File.expand_path("bundler/installer", __dir__)
|
||||
autoload :LazySpecification, File.expand_path("bundler/lazy_specification", __dir__)
|
||||
autoload :LockfileParser, File.expand_path("bundler/lockfile_parser", __dir__)
|
||||
autoload :MatchRemoteMetadata, File.expand_path("bundler/match_remote_metadata", __dir__)
|
||||
autoload :Materialization, File.expand_path("bundler/materialization", __dir__)
|
||||
autoload :NULL, File.expand_path("bundler/constants", __dir__)
|
||||
autoload :ProcessLock, File.expand_path("bundler/process_lock", __dir__)
|
||||
autoload :RemoteSpecification, File.expand_path("bundler/remote_specification", __dir__)
|
||||
autoload :Resolver, File.expand_path("bundler/resolver", __dir__)
|
||||
autoload :Retry, File.expand_path("bundler/retry", __dir__)
|
||||
autoload :RubyDsl, File.expand_path("bundler/ruby_dsl", __dir__)
|
||||
autoload :RubyVersion, File.expand_path("bundler/ruby_version", __dir__)
|
||||
autoload :Runtime, File.expand_path("bundler/runtime", __dir__)
|
||||
autoload :SelfManager, File.expand_path("bundler/self_manager", __dir__)
|
||||
autoload :Settings, File.expand_path("bundler/settings", __dir__)
|
||||
autoload :SharedHelpers, File.expand_path("bundler/shared_helpers", __dir__)
|
||||
autoload :Source, File.expand_path("bundler/source", __dir__)
|
||||
autoload :SourceList, File.expand_path("bundler/source_list", __dir__)
|
||||
autoload :SourceMap, File.expand_path("bundler/source_map", __dir__)
|
||||
autoload :SpecSet, File.expand_path("bundler/spec_set", __dir__)
|
||||
autoload :StubSpecification, File.expand_path("bundler/stub_specification", __dir__)
|
||||
autoload :UI, File.expand_path("bundler/ui", __dir__)
|
||||
autoload :URICredentialsFilter, File.expand_path("bundler/uri_credentials_filter", __dir__)
|
||||
autoload :URINormalizer, File.expand_path("bundler/uri_normalizer", __dir__)
|
||||
autoload :WINDOWS, File.expand_path("bundler/constants", __dir__)
|
||||
autoload :SafeMarshal, File.expand_path("bundler/safe_marshal", __dir__)
|
||||
|
||||
class << self
|
||||
def configure
|
||||
@configure ||= configure_gem_home_and_path
|
||||
end
|
||||
|
||||
def ui
|
||||
(defined?(@ui) && @ui) || (self.ui = UI::Shell.new)
|
||||
end
|
||||
|
||||
def ui=(ui)
|
||||
Bundler.rubygems.ui = UI::RGProxy.new(ui)
|
||||
@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 create_bundle_path
|
||||
mkdir_p(bundle_path) unless bundle_path.exist?
|
||||
|
||||
@bundle_path = bundle_path.realpath
|
||||
rescue Errno::EEXIST
|
||||
raise PathError, "Could not install to path `#{bundle_path}` " \
|
||||
"because a file already exists at that path. Either remove or rename the file so the directory can be created."
|
||||
end
|
||||
|
||||
def configured_bundle_path
|
||||
@configured_bundle_path ||= Bundler.settings.path.tap(&:validate!)
|
||||
end
|
||||
|
||||
# Returns absolute location of where binstubs are installed to.
|
||||
def bin_path
|
||||
@bin_path ||= begin
|
||||
path = Bundler.settings[:bin] || "bin"
|
||||
path = Pathname.new(path).expand_path(root).expand_path
|
||||
mkdir_p(path)
|
||||
path
|
||||
end
|
||||
end
|
||||
|
||||
# Turns on the Bundler runtime. After +Bundler.setup+ call, all +load+ or
|
||||
# +require+ of the gems would be allowed only if they are part of
|
||||
# the Gemfile or Ruby's standard library. If the versions specified
|
||||
# in Gemfile, only those versions would be loaded.
|
||||
#
|
||||
# Assuming Gemfile
|
||||
#
|
||||
# gem 'first_gem', '= 1.0'
|
||||
# group :test do
|
||||
# gem 'second_gem', '= 1.0'
|
||||
# end
|
||||
#
|
||||
# The code using Bundler.setup works as follows:
|
||||
#
|
||||
# require 'third_gem' # allowed, required from global gems
|
||||
# require 'first_gem' # allowed, loads the last installed version
|
||||
# Bundler.setup
|
||||
# require 'fourth_gem' # fails with LoadError
|
||||
# require 'second_gem' # loads exactly version 1.0
|
||||
#
|
||||
# +Bundler.setup+ can be called only once, all subsequent calls are no-op.
|
||||
#
|
||||
# If _groups_ list is provided, only gems from specified groups would
|
||||
# be allowed (gems specified outside groups belong to special +:default+ group).
|
||||
#
|
||||
# To require all gems from Gemfile (or only some groups), see Bundler.require.
|
||||
#
|
||||
def setup(*groups)
|
||||
# Return if all groups are already loaded
|
||||
return @setup if defined?(@setup) && @setup
|
||||
|
||||
configure_custom_gemfile
|
||||
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 auto_switch
|
||||
self_manager.restart_with_locked_bundler_if_needed
|
||||
end
|
||||
|
||||
# Automatically install dependencies if <tt>settings[:auto_install]</tt> exists.
|
||||
# This is set through config cmd `bundle config set --global 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
|
||||
definition.specs
|
||||
rescue GemNotFound, GitError
|
||||
ui.info "Automatically installing missing gems."
|
||||
reset!
|
||||
CLI::Install.new({}).run
|
||||
reset!
|
||||
end
|
||||
end
|
||||
|
||||
# Setups Bundler environment (see Bundler.setup) if it is not already set,
|
||||
# and loads all gems from groups specified. Unlike ::setup, can be called
|
||||
# multiple times with different groups (if they were allowed by setup).
|
||||
#
|
||||
# Assuming Gemfile
|
||||
#
|
||||
# gem 'first_gem', '= 1.0'
|
||||
# group :test do
|
||||
# gem 'second_gem', '= 1.0'
|
||||
# end
|
||||
#
|
||||
# The code will work as follows:
|
||||
#
|
||||
# Bundler.setup # allow all groups
|
||||
# Bundler.require(:default) # requires only first_gem
|
||||
# # ...later
|
||||
# Bundler.require(:test) # requires second_gem
|
||||
#
|
||||
def require(*groups)
|
||||
setup(*groups).require(*groups)
|
||||
end
|
||||
|
||||
def load
|
||||
@load ||= Runtime.new(root, definition)
|
||||
end
|
||||
|
||||
def environment
|
||||
SharedHelpers.feature_removed! "Bundler.environment has been removed in favor of Bundler.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
|
||||
# @param lockfile [Pathname] Path to Gemfile.lock
|
||||
# @return [Bundler::Definition]
|
||||
def definition(unlock = nil, lockfile = default_lockfile)
|
||||
@definition = nil if unlock
|
||||
@definition ||= begin
|
||||
configure
|
||||
Definition.build(default_gemfile, lockfile, unlock)
|
||||
end
|
||||
end
|
||||
|
||||
def frozen_bundle?
|
||||
frozen = Bundler.settings[:frozen]
|
||||
return frozen unless frozen.nil?
|
||||
|
||||
Bundler.settings[:deployment]
|
||||
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}/#{RbConfig::CONFIG["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
|
||||
Bundler.ui.warn "#{warning}\n"
|
||||
user_home = tmp_home_path
|
||||
Bundler.ui.warn "Bundler will use `#{user_home}' as your home directory temporarily.\n"
|
||||
user_home
|
||||
else
|
||||
Pathname.new(home)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_bundle_path(dir = "home")
|
||||
env_var, fallback = case dir
|
||||
when "home"
|
||||
["BUNDLE_USER_HOME", proc { Pathname.new(user_home).join(".bundle") }]
|
||||
when "cache"
|
||||
["BUNDLE_USER_CACHE", proc { user_bundle_path.join("cache") }]
|
||||
when "config"
|
||||
["BUNDLE_USER_CONFIG", proc { user_bundle_path.join("config") }]
|
||||
when "plugin"
|
||||
["BUNDLE_USER_PLUGIN", proc { 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"]
|
||||
app_config_pathname = Pathname.new(app_config)
|
||||
|
||||
if app_config_pathname.absolute?
|
||||
app_config_pathname
|
||||
else
|
||||
app_config_pathname.expand_path(root)
|
||||
end
|
||||
else
|
||||
root.join(".bundle")
|
||||
end
|
||||
end
|
||||
|
||||
def app_cache(custom_path = nil)
|
||||
path = custom_path || root
|
||||
Pathname.new(path).join(Bundler.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)
|
||||
end
|
||||
|
||||
def settings
|
||||
@settings ||= Settings.new(app_config_path)
|
||||
rescue GemfileNotFound
|
||||
@settings = Settings.new
|
||||
end
|
||||
|
||||
# @return [Hash] Environment present before Bundler was activated
|
||||
def original_env
|
||||
ORIGINAL_ENV.clone
|
||||
end
|
||||
|
||||
def clean_env
|
||||
removed_message =
|
||||
"`Bundler.clean_env` has been removed in favor of `Bundler.unbundled_env`. " \
|
||||
"If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`"
|
||||
Bundler::SharedHelpers.feature_removed!(removed_message)
|
||||
end
|
||||
|
||||
# @return [Hash] Environment with all bundler-related variables removed
|
||||
def unbundled_env
|
||||
unbundle_env(original_env)
|
||||
end
|
||||
|
||||
# Remove all bundler-related variables from ENV
|
||||
def unbundle_env!
|
||||
ENV.replace(unbundle_env(ENV))
|
||||
end
|
||||
|
||||
# Run block with environment present before Bundler was activated
|
||||
def with_original_env
|
||||
with_env(original_env) { yield }
|
||||
end
|
||||
|
||||
def with_clean_env
|
||||
removed_message =
|
||||
"`Bundler.with_clean_env` has been removed in favor of `Bundler.with_unbundled_env`. " \
|
||||
"If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`"
|
||||
Bundler::SharedHelpers.feature_removed!(removed_message)
|
||||
end
|
||||
|
||||
# Run block with all bundler-related variables removed
|
||||
def with_unbundled_env
|
||||
with_env(unbundled_env) { yield }
|
||||
end
|
||||
|
||||
# Run subcommand with the environment present before Bundler was activated
|
||||
def original_system(*args)
|
||||
with_original_env { Kernel.system(*args) }
|
||||
end
|
||||
|
||||
def clean_system(*args)
|
||||
removed_message =
|
||||
"`Bundler.clean_system` has been removed in favor of `Bundler.unbundled_system`. " \
|
||||
"If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system`"
|
||||
Bundler::SharedHelpers.feature_removed!(removed_message)
|
||||
end
|
||||
|
||||
# Run subcommand in an environment with all bundler related variables removed
|
||||
def unbundled_system(*args)
|
||||
with_unbundled_env { Kernel.system(*args) }
|
||||
end
|
||||
|
||||
# Run a `Kernel.exec` to a subcommand with the environment present before Bundler was activated
|
||||
def original_exec(*args)
|
||||
with_original_env { Kernel.exec(*args) }
|
||||
end
|
||||
|
||||
def clean_exec(*args)
|
||||
removed_message =
|
||||
"`Bundler.clean_exec` has been removed in favor of `Bundler.unbundled_exec`. " \
|
||||
"If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec`"
|
||||
Bundler::SharedHelpers.feature_removed!(removed_message)
|
||||
end
|
||||
|
||||
# Run a `Kernel.exec` to a subcommand in an environment with all bundler related variables removed
|
||||
def unbundled_exec(*args)
|
||||
with_env(unbundled_env) { Kernel.exec(*args) }
|
||||
end
|
||||
|
||||
def local_platform
|
||||
return Gem::Platform::RUBY if Bundler.settings[:force_ruby_platform]
|
||||
Gem::Platform.local
|
||||
end
|
||||
|
||||
def generic_local_platform
|
||||
Gem::Platform.generic(local_platform)
|
||||
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 set --local bindir foo`.
|
||||
Bundler.settings[:system_bindir] || Bundler.rubygems.gem_bindir
|
||||
end
|
||||
|
||||
def preferred_gemfile_name
|
||||
Bundler.settings[:init_gems_rb] ? "gems.rb" : "Gemfile"
|
||||
end
|
||||
|
||||
def use_system_gems?
|
||||
configured_bundle_path.use_system_gems?
|
||||
end
|
||||
|
||||
def mkdir_p(path)
|
||||
SharedHelpers.filesystem_access(path, :create) do |p|
|
||||
FileUtils.mkdir_p(p)
|
||||
end
|
||||
end
|
||||
|
||||
def which(executable)
|
||||
executable_path = find_executable(executable)
|
||||
return executable_path if executable_path
|
||||
|
||||
if (paths = ENV["PATH"])
|
||||
quote = '"'
|
||||
paths.split(File::PATH_SEPARATOR).find do |path|
|
||||
path = path[1..-2] if path.start_with?(quote) && path.end_with?(quote)
|
||||
executable_path = find_executable(File.expand_path(executable, path))
|
||||
return executable_path if executable_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_executable(path)
|
||||
extensions = RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split
|
||||
extensions = [RbConfig::CONFIG["EXEEXT"]] unless extensions&.any?
|
||||
candidates = extensions.map {|ext| "#{path}#{ext}" }
|
||||
|
||||
candidates.find {|candidate| File.file?(candidate) && File.executable?(candidate) }
|
||||
end
|
||||
|
||||
def read_file(file)
|
||||
SharedHelpers.filesystem_access(file, :read) do
|
||||
File.open(file, "r:UTF-8", &:read)
|
||||
end
|
||||
end
|
||||
|
||||
def safe_load_marshal(data)
|
||||
if Gem.respond_to?(:load_safe_marshal)
|
||||
Gem.load_safe_marshal
|
||||
begin
|
||||
Gem::SafeMarshal.safe_load(data)
|
||||
rescue Gem::SafeMarshal::Reader::Error, Gem::SafeMarshal::Visitors::ToRuby::Error => e
|
||||
raise MarshalError, "#{e.class}: #{e.message}"
|
||||
end
|
||||
else
|
||||
load_marshal(data, marshal_proc: SafeMarshal.proc)
|
||||
end
|
||||
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
|
||||
end
|
||||
|
||||
def load_gemspec_uncached(file, validate = false)
|
||||
path = Pathname.new(file).expand_path
|
||||
contents = read_file(path.to_s)
|
||||
spec = eval_gemspec(path, contents)
|
||||
return unless spec
|
||||
spec.loaded_from = 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")
|
||||
end
|
||||
|
||||
def feature_flag
|
||||
@feature_flag ||= FeatureFlag.new(Bundler.settings[:simulate_version] || VERSION)
|
||||
end
|
||||
|
||||
def reset!
|
||||
reset_paths!
|
||||
Plugin.reset!
|
||||
reset_rubygems!
|
||||
end
|
||||
|
||||
def reset_settings_and_root!
|
||||
@settings = nil
|
||||
@root = nil
|
||||
end
|
||||
|
||||
def reset_paths!
|
||||
@bin_path = nil
|
||||
@bundle_path = nil
|
||||
@configure = 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
|
||||
|
||||
def configure_gem_home_and_path(path = bundle_path)
|
||||
configure_gem_path
|
||||
configure_gem_home(path)
|
||||
Bundler.rubygems.clear_paths
|
||||
end
|
||||
|
||||
def configure_custom_gemfile(custom_gemfile = nil)
|
||||
custom_gemfile ||= Bundler.settings[:gemfile]
|
||||
|
||||
if custom_gemfile && !custom_gemfile.empty?
|
||||
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
|
||||
reset_settings_and_root!
|
||||
end
|
||||
end
|
||||
|
||||
def self_manager
|
||||
@self_manager ||= begin
|
||||
require_relative "bundler/self_manager"
|
||||
Bundler::SelfManager.new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unbundle_env(env)
|
||||
if env.key?("BUNDLER_ORIG_MANPATH")
|
||||
env["MANPATH"] = env["BUNDLER_ORIG_MANPATH"]
|
||||
end
|
||||
|
||||
env.delete_if {|k, _| k[0, 7] == "BUNDLE_" }
|
||||
env.delete("BUNDLER_SETUP")
|
||||
|
||||
if env.key?("RUBYOPT")
|
||||
rubyopt = env["RUBYOPT"].split(" ")
|
||||
rubyopt.delete("-r#{File.expand_path("bundler/setup", __dir__)}")
|
||||
rubyopt.delete("-rbundler/setup")
|
||||
env["RUBYOPT"] = rubyopt.join(" ")
|
||||
end
|
||||
|
||||
if env.key?("RUBYLIB")
|
||||
rubylib = env["RUBYLIB"].split(File::PATH_SEPARATOR)
|
||||
rubylib.delete(__dir__)
|
||||
env["RUBYLIB"] = rubylib.join(File::PATH_SEPARATOR)
|
||||
end
|
||||
|
||||
env
|
||||
end
|
||||
|
||||
def load_marshal(data, marshal_proc: nil)
|
||||
Marshal.load(data, marshal_proc)
|
||||
rescue TypeError => e
|
||||
raise MarshalError, "#{e.class}: #{e.message}"
|
||||
end
|
||||
|
||||
def eval_yaml_gemspec(path, contents)
|
||||
Kernel.require "psych"
|
||||
|
||||
Gem::Specification.from_yaml(contents)
|
||||
end
|
||||
|
||||
def eval_gemspec(path, contents)
|
||||
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. The caller expands `path` first, since
|
||||
# expanding it here would resolve against the gemspec directory once
|
||||
# the chdir is active.
|
||||
SharedHelpers.chdir(path.dirname.to_s) do
|
||||
# TOPLEVEL_BINDING always belongs to the main box, so inside a
|
||||
# Ruby::Box use a binding from the box Bundler is loaded in, where
|
||||
# Gem::Specification carries Bundler's own monkey patches.
|
||||
eval_binding = if defined?(Ruby::Box) && Ruby::Box.enabled?
|
||||
Ruby::Box.current.eval("binding")
|
||||
else
|
||||
TOPLEVEL_BINDING.dup
|
||||
end
|
||||
eval(contents, eval_binding, path.to_s)
|
||||
end
|
||||
end
|
||||
rescue ScriptError, StandardError => e
|
||||
msg = "There was an error while loading `#{path.basename}`: #{e.message}"
|
||||
|
||||
raise GemspecError, Dsl::DSLError.new(msg, path.to_s, e.backtrace, contents)
|
||||
end
|
||||
|
||||
def configure_gem_path
|
||||
unless 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.
|
||||
Bundler::SharedHelpers.set_env "GEM_PATH", ""
|
||||
end
|
||||
end
|
||||
|
||||
def configure_gem_home(path)
|
||||
Bundler::SharedHelpers.set_env "GEM_HOME", path.to_s
|
||||
end
|
||||
|
||||
def tmp_home_path
|
||||
Kernel.send(:require, "tmpdir")
|
||||
SharedHelpers.filesystem_access(Dir.tmpdir) do
|
||||
path = Bundler.tmp
|
||||
at_exit { Bundler.rm_rf(path) }
|
||||
path
|
||||
end
|
||||
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 @@
|
||||
# not in RDoc
|
||||
@@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# Represents metadata from when the Bundler gem was built.
|
||||
module BuildMetadata
|
||||
# begin ivars
|
||||
@built_at = "2026-09-16".freeze
|
||||
@git_commit_sha = "a15d54ed692".freeze
|
||||
# end ivars
|
||||
|
||||
# A hash representation of the build metadata.
|
||||
def self.to_h
|
||||
{
|
||||
"Timestamp" => timestamp,
|
||||
"Git SHA" => git_commit_sha,
|
||||
}
|
||||
end
|
||||
|
||||
# A timestamp representing the date the bundler gem was built, or the
|
||||
# current time if never built
|
||||
def self.timestamp
|
||||
@timestamp ||= @built_at || Time.now.utc.strftime("%Y-%m-%d").freeze
|
||||
end
|
||||
|
||||
# A string representing the date the bundler gem was built.
|
||||
def self.built_at
|
||||
@built_at
|
||||
end
|
||||
|
||||
# The SHA for the git commit the bundler gem was built from.
|
||||
def self.git_commit_sha
|
||||
return @git_commit_sha if instance_variable_defined? :@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.expand_path("../../../.git", __dir__)
|
||||
if File.directory?(git_dir)
|
||||
return @git_commit_sha = IO.popen(%w[git rev-parse --short HEAD], { chdir: git_dir }, &:read).strip.freeze
|
||||
end
|
||||
|
||||
@git_commit_sha ||= "unknown"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "shared_helpers"
|
||||
Bundler::SharedHelpers.feature_removed! "The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler"
|
||||
@@ -0,0 +1,270 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Checksum
|
||||
ALGO_SEPARATOR = "="
|
||||
DEFAULT_ALGORITHM = "sha256"
|
||||
private_constant :DEFAULT_ALGORITHM
|
||||
DEFAULT_BLOCK_SIZE = 16_384
|
||||
private_constant :DEFAULT_BLOCK_SIZE
|
||||
|
||||
class << self
|
||||
def from_gem_package(gem_package, algo = DEFAULT_ALGORITHM)
|
||||
return if Bundler.settings[:disable_checksum_validation]
|
||||
return unless source = gem_package.instance_variable_get(:@gem)
|
||||
return unless source.respond_to?(:with_read_io)
|
||||
|
||||
source.with_read_io do |io|
|
||||
from_gem(io, source.path)
|
||||
ensure
|
||||
io.rewind
|
||||
end
|
||||
end
|
||||
|
||||
def from_gem(io, pathname, algo = DEFAULT_ALGORITHM)
|
||||
digest = Bundler::SharedHelpers.digest(algo.upcase).new
|
||||
buf = String.new(capacity: DEFAULT_BLOCK_SIZE)
|
||||
digest << io.readpartial(DEFAULT_BLOCK_SIZE, buf) until io.eof?
|
||||
Checksum.new(algo, digest.hexdigest!, Source.new(:gem, pathname))
|
||||
end
|
||||
|
||||
def from_api(digest, source_uri, algo = DEFAULT_ALGORITHM)
|
||||
return if Bundler.settings[:disable_checksum_validation]
|
||||
|
||||
Checksum.new(algo, to_hexdigest(digest, algo), Source.new(:api, source_uri))
|
||||
end
|
||||
|
||||
def from_lock(lock_checksum, lockfile_location)
|
||||
algo, digest = lock_checksum.strip.split(ALGO_SEPARATOR, 2)
|
||||
Checksum.new(algo, to_hexdigest(digest, algo), Source.new(:lock, lockfile_location))
|
||||
end
|
||||
|
||||
def to_hexdigest(digest, algo = DEFAULT_ALGORITHM)
|
||||
return digest unless algo == DEFAULT_ALGORITHM
|
||||
return digest if digest.match?(/\A[0-9a-f]{64}\z/i)
|
||||
|
||||
if digest.match?(%r{\A[-0-9a-z_+/]{43}={0,2}\z}i)
|
||||
digest = digest.tr("-_", "+/") # fix urlsafe base64
|
||||
digest.unpack1("m0").unpack1("H*")
|
||||
else
|
||||
raise ArgumentError, "#{digest.inspect} is not a valid SHA256 hex or base64 digest"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :algo, :digest, :sources
|
||||
|
||||
def initialize(algo, digest, source)
|
||||
@algo = algo
|
||||
@digest = digest
|
||||
@sources = [source]
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
match?(other) && other.sources == sources
|
||||
end
|
||||
|
||||
alias_method :eql?, :==
|
||||
|
||||
def same_source?(other)
|
||||
sources.include?(other.sources.first)
|
||||
end
|
||||
|
||||
def match?(other)
|
||||
other.is_a?(self.class) && other.digest == digest && other.algo == algo
|
||||
end
|
||||
|
||||
def hash
|
||||
digest.hash
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{to_lock} (from #{sources.first}#{", ..." if sources.size > 1})"
|
||||
end
|
||||
|
||||
def to_lock
|
||||
"#{algo}#{ALGO_SEPARATOR}#{digest}"
|
||||
end
|
||||
|
||||
def merge!(other)
|
||||
return nil unless match?(other)
|
||||
|
||||
@sources.concat(other.sources).uniq!
|
||||
self
|
||||
end
|
||||
|
||||
def formatted_sources
|
||||
sources.join("\n and ").concat("\n")
|
||||
end
|
||||
|
||||
def removable?
|
||||
sources.all?(&:removable?)
|
||||
end
|
||||
|
||||
def removal_instructions
|
||||
msg = +""
|
||||
i = 1
|
||||
sources.each do |source|
|
||||
msg << " #{i}. #{source.removal}\n"
|
||||
i += 1
|
||||
end
|
||||
msg << " #{i}. run `bundle install`\n"
|
||||
end
|
||||
|
||||
def inspect
|
||||
abbr = "#{algo}#{ALGO_SEPARATOR}#{digest[0, 8]}"
|
||||
from = "from #{sources.join(" and ")}"
|
||||
"#<#{self.class}:#{object_id} #{abbr} #{from}>"
|
||||
end
|
||||
|
||||
class Source
|
||||
attr_reader :type, :location
|
||||
|
||||
def initialize(type, location)
|
||||
@type = type
|
||||
@location = location
|
||||
end
|
||||
|
||||
def removable?
|
||||
[:lock, :gem].include?(type)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.is_a?(self.class) && other.type == type && other.location == location
|
||||
end
|
||||
|
||||
# phrased so that the usual string format is grammatically correct
|
||||
# rake (10.3.2) sha256=abc123 from #{to_s}
|
||||
def to_s
|
||||
case type
|
||||
when :lock
|
||||
"the lockfile CHECKSUMS at #{location}"
|
||||
when :gem
|
||||
"the gem at #{location}"
|
||||
when :api
|
||||
"the API at #{location}"
|
||||
else
|
||||
"#{location} (#{type})"
|
||||
end
|
||||
end
|
||||
|
||||
# A full sentence describing how to remove the checksum
|
||||
def removal
|
||||
case type
|
||||
when :lock
|
||||
"remove the matching checksum in #{location}"
|
||||
when :gem
|
||||
"remove the gem at #{location}"
|
||||
when :api
|
||||
"checksums from #{location} cannot be locally modified, you may need to update your sources"
|
||||
else
|
||||
"remove #{location} (#{type})"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Store
|
||||
attr_reader :store
|
||||
protected :store
|
||||
|
||||
def initialize
|
||||
@store = {}
|
||||
@store_mutex = Mutex.new
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:#{object_id} size=#{store.size}>"
|
||||
end
|
||||
|
||||
# Replace when the new checksum is from the same source.
|
||||
# The primary purpose is registering checksums from gems where there are
|
||||
# duplicates of the same gem (according to full_name) in the index.
|
||||
#
|
||||
# In particular, this is when 2 gems have two similar platforms, e.g.
|
||||
# "darwin20" and "darwin-20", both of which resolve to darwin-20.
|
||||
# In the Index, the later gem replaces the former, so we do that here.
|
||||
#
|
||||
# However, if the new checksum is from a different source, we register like normal.
|
||||
# This ensures a mismatch error where there are multiple top level sources
|
||||
# that contain the same gem with different checksums.
|
||||
def replace(spec, checksum)
|
||||
return unless checksum
|
||||
|
||||
lock_name = spec.lock_name
|
||||
@store_mutex.synchronize do
|
||||
existing = fetch_checksum(lock_name, checksum.algo)
|
||||
if !existing || existing.same_source?(checksum)
|
||||
store_checksum(lock_name, checksum)
|
||||
else
|
||||
merge_checksum(lock_name, checksum, existing)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def missing?(spec)
|
||||
@store[spec.lock_name].nil?
|
||||
end
|
||||
|
||||
def empty?(spec)
|
||||
return false unless spec.source.is_a?(Bundler::Source::Rubygems)
|
||||
|
||||
@store[spec.lock_name].empty?
|
||||
end
|
||||
|
||||
def register(spec, checksum)
|
||||
register_checksum(spec.lock_name, checksum)
|
||||
end
|
||||
|
||||
def merge!(other)
|
||||
other.store.each do |lock_name, checksums|
|
||||
checksums.each do |_algo, checksum|
|
||||
register_checksum(lock_name, checksum)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_lock(spec)
|
||||
lock_name = spec.lock_name
|
||||
checksums = @store[lock_name]
|
||||
if checksums&.any?
|
||||
"#{lock_name} #{checksums.values.map(&:to_lock).sort.join(",")}"
|
||||
else
|
||||
lock_name
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def register_checksum(lock_name, checksum)
|
||||
@store_mutex.synchronize do
|
||||
if checksum
|
||||
existing = fetch_checksum(lock_name, checksum.algo)
|
||||
if existing
|
||||
merge_checksum(lock_name, checksum, existing)
|
||||
else
|
||||
store_checksum(lock_name, checksum)
|
||||
end
|
||||
else
|
||||
init_checksum(lock_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merge_checksum(lock_name, checksum, existing)
|
||||
existing.merge!(checksum) || raise(ChecksumMismatchError.new(lock_name, existing, checksum))
|
||||
end
|
||||
|
||||
def store_checksum(lock_name, checksum)
|
||||
init_checksum(lock_name)[checksum.algo] = checksum
|
||||
end
|
||||
|
||||
def init_checksum(lock_name)
|
||||
@store[lock_name] ||= {}
|
||||
end
|
||||
|
||||
def fetch_checksum(lock_name, algo)
|
||||
@store[lock_name]&.fetch(algo, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module CIDetector
|
||||
# NOTE: Any changes made here will need to be made to both lib/rubygems/ci_detector.rb and
|
||||
# bundler/lib/bundler/ci_detector.rb (which are enforced duplicates).
|
||||
# TODO: Drop that duplication once bundler drops support for RubyGems 3.4
|
||||
#
|
||||
# ## Recognized CI providers, their signifiers, and the relevant docs ##
|
||||
#
|
||||
# Travis CI - CI, TRAVIS https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
|
||||
# Cirrus CI - CI, CIRRUS_CI https://cirrus-ci.org/guide/writing-tasks/#environment-variables
|
||||
# Circle CI - CI, CIRCLECI https://circleci.com/docs/variables/#built-in-environment-variables
|
||||
# Gitlab CI - CI, GITLAB_CI https://docs.gitlab.com/ee/ci/variables/
|
||||
# AppVeyor - CI, APPVEYOR https://www.appveyor.com/docs/environment-variables/
|
||||
# CodeShip - CI_NAME https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables#_default_environment_variables
|
||||
# dsari - CI, DSARI https://github.com/rfinnie/dsari#running
|
||||
# Jenkins - BUILD_NUMBER https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
|
||||
# TeamCity - TEAMCITY_VERSION https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Predefined+Server+Build+Parameters
|
||||
# Appflow - CI_BUILD_ID https://ionic.io/docs/appflow/automation/environments#predefined-environments
|
||||
# TaskCluster - TASKCLUSTER_ROOT_URL https://docs.taskcluster.net/docs/manual/design/env-vars
|
||||
# Semaphore - CI, SEMAPHORE https://docs.semaphoreci.com/ci-cd-environment/environment-variables/
|
||||
# BuildKite - CI, BUILDKITE https://buildkite.com/docs/pipelines/environment-variables
|
||||
# GoCD - GO_SERVER_URL https://docs.gocd.org/current/faq/dev_use_current_revision_in_build.html
|
||||
# GH Actions - CI, GITHUB_ACTIONS https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
|
||||
#
|
||||
# ### Some "standard" ENVs that multiple providers may set ###
|
||||
#
|
||||
# * CI - this is set by _most_ (but not all) CI providers now; it's approaching a standard.
|
||||
# * CI_NAME - Not as frequently used, but some providers set this to specify their own name
|
||||
|
||||
# Any of these being set is a reasonably reliable indicator that we are
|
||||
# executing in a CI environment.
|
||||
ENV_INDICATORS = [
|
||||
"CI",
|
||||
"CI_NAME",
|
||||
"CONTINUOUS_INTEGRATION",
|
||||
"BUILD_NUMBER",
|
||||
"CI_APP_ID",
|
||||
"CI_BUILD_ID",
|
||||
"CI_BUILD_NUMBER",
|
||||
"RUN_ID",
|
||||
"TASKCLUSTER_ROOT_URL",
|
||||
].freeze
|
||||
|
||||
# For each CI, this env suffices to indicate that we're on _that_ CI's
|
||||
# containers. (A few of them only supply a CI_NAME variable, which is also
|
||||
# nice). And if they set "CI" but we can't tell which one they are, we also
|
||||
# want to know that - a bare "ci" without another token tells us as much.
|
||||
ENV_DESCRIPTORS = {
|
||||
"TRAVIS" => "travis",
|
||||
"CIRCLECI" => "circle",
|
||||
"CIRRUS_CI" => "cirrus",
|
||||
"DSARI" => "dsari",
|
||||
"SEMAPHORE" => "semaphore",
|
||||
"JENKINS_URL" => "jenkins",
|
||||
"BUILDKITE" => "buildkite",
|
||||
"GO_SERVER_URL" => "go",
|
||||
"GITLAB_CI" => "gitlab",
|
||||
"GITHUB_ACTIONS" => "github",
|
||||
"TASKCLUSTER_ROOT_URL" => "taskcluster",
|
||||
"CI" => "ci",
|
||||
}.freeze
|
||||
|
||||
def self.ci?
|
||||
ENV_INDICATORS.any? {|var| ENV.include?(var) }
|
||||
end
|
||||
|
||||
def self.ci_strings
|
||||
matching_names = ENV_DESCRIPTORS.select {|env, _| ENV[env] }.values
|
||||
matching_names << ENV["CI_NAME"].downcase if ENV["CI_NAME"]
|
||||
matching_names.reject(&:empty?).sort.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,831 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "vendored_thor"
|
||||
|
||||
module Bundler
|
||||
class CLI < Thor
|
||||
require_relative "cli/common"
|
||||
require_relative "cli/install"
|
||||
|
||||
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
|
||||
EXTENSIONS = ["c", "rust", "go"].freeze
|
||||
|
||||
COMMAND_ALIASES = {
|
||||
"check" => "c",
|
||||
"install" => "i",
|
||||
"plugin" => "",
|
||||
"list" => "ls",
|
||||
"exec" => ["e", "ex", "exe"],
|
||||
"cache" => ["package", "pack"],
|
||||
"version" => ["-v", "--version"],
|
||||
}.freeze
|
||||
|
||||
def self.start(*)
|
||||
check_invalid_ext_option(ARGV) if ARGV.include?("--ext")
|
||||
|
||||
super
|
||||
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 self.all_aliases
|
||||
@all_aliases ||= begin
|
||||
command_aliases = {}
|
||||
|
||||
COMMAND_ALIASES.each do |name, aliases|
|
||||
Array(aliases).each do |one_alias|
|
||||
command_aliases[one_alias] = name
|
||||
end
|
||||
end
|
||||
|
||||
command_aliases
|
||||
end
|
||||
end
|
||||
|
||||
def self.aliases_for(command_name)
|
||||
COMMAND_ALIASES.select {|k, _| k == command_name }.invert
|
||||
end
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
current_cmd = args.last[:current_command].name
|
||||
|
||||
# `bundle config` manages stored settings, so avoid promoting settings
|
||||
# like `gemfile` or `lockfile` to environment variables before it runs.
|
||||
unless current_cmd == "config"
|
||||
Bundler.configure_custom_gemfile(options[:gemfile])
|
||||
|
||||
# lock --lockfile works differently than install --lockfile
|
||||
unless current_cmd == "lock"
|
||||
custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile]
|
||||
if custom_lockfile && !custom_lockfile.empty?
|
||||
Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile)
|
||||
reset_settings = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Bundler.reset_settings_and_root! if reset_settings
|
||||
|
||||
Bundler.auto_switch
|
||||
|
||||
Bundler.settings.set_command_option_if_given :retry, options[:retry]
|
||||
|
||||
Bundler.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] || Bundler.settings[:verbose]
|
||||
unprinted_warnings.each {|w| Bundler.ui.warn(w) }
|
||||
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", "cache", "exec", "config", "help"]
|
||||
|
||||
list = self.class.printable_commands(true)
|
||||
by_name = list.group_by {|name, _message| name.match(/^bundler? (\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
|
||||
|
||||
desc "install_or_cli_help", "Deprecated alias of install", hide: true
|
||||
def install_or_cli_help
|
||||
Bundler.ui.warn <<~MSG
|
||||
`bundle install_or_cli_help` is a deprecated alias of `bundle install`.
|
||||
It might be called due to the 'default_cli_command' being set to 'install_or_cli_help',
|
||||
if so fix that by running `bundle config set default_cli_command install --global`.
|
||||
MSG
|
||||
invoke_other_command("install")
|
||||
end
|
||||
|
||||
def self.default_command(meth = nil)
|
||||
return super if meth
|
||||
|
||||
unless Bundler.settings[:default_cli_command]
|
||||
Bundler.ui.info <<~MSG
|
||||
In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`.
|
||||
Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD.
|
||||
You can use the future behavior now with `bundle config set default_cli_command cli_help --global`,
|
||||
or you can continue to use the current behavior with `bundle config set default_cli_command install --global`.
|
||||
This message will be removed after a default_cli_command value is set.
|
||||
|
||||
MSG
|
||||
end
|
||||
|
||||
Bundler.settings[:default_cli_command] || "install"
|
||||
end
|
||||
|
||||
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)
|
||||
cli = self.class.all_aliases[cli] if self.class.all_aliases[cli]
|
||||
|
||||
if Bundler.settings[:plugins] && Bundler::Plugin.command?(cli) && !self.class.all_commands.key?(cli)
|
||||
return Bundler::Plugin.exec_command(cli, ["--help"])
|
||||
end
|
||||
|
||||
case cli
|
||||
when "gemfile" then command = "gemfile"
|
||||
when nil then command = "bundle"
|
||||
else command = "bundle-#{cli}"
|
||||
end
|
||||
|
||||
man_path = File.expand_path("man", __dir__)
|
||||
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)
|
||||
man_page = man_pages[command]
|
||||
if Bundler.which("man") && !man_path.match?(%r{^(?:file:/.+!|uri:classloader:)/META-INF/jruby.home/.+})
|
||||
Kernel.exec("man", man_page)
|
||||
else
|
||||
puts File.read("#{man_path}/#{File.basename(man_page)}.ronn")
|
||||
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.settings[: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
|
||||
method_option "gemspec", type: :string, banner: "Use the specified .gemspec to create the Gemfile"
|
||||
method_option "gemfile", type: :string, banner: "Use the specified name for the gemfile instead of 'Gemfile'"
|
||||
def init
|
||||
require_relative "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, namely, $BUNDLE_PATH or $GEM_HOME (removed)"
|
||||
def check
|
||||
remembered_flag_deprecation("path")
|
||||
|
||||
require_relative "cli/check"
|
||||
Check.new(options).run
|
||||
end
|
||||
|
||||
map aliases_for("check")
|
||||
|
||||
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 (removed)"
|
||||
def remove(*gems)
|
||||
if ARGV.include?("--install")
|
||||
removed_message = "The `--install` flag has been removed. `bundle install` is triggered by default."
|
||||
raise InvalidOption, removed_message
|
||||
end
|
||||
|
||||
require_relative "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
|
||||
method_option "binstubs", type: :string, lazy_default: "bin", banner: "Generate bin stubs for bundled gems to ./bin (removed)"
|
||||
method_option "clean", type: :boolean, banner: "Run bundle clean automatically after install (removed)"
|
||||
method_option "deployment", type: :boolean, banner: "Install using defaults tuned for deployment environments (removed)"
|
||||
method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this install (removed)"
|
||||
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"
|
||||
method_option "lockfile", type: :string, banner: "Use the specified lockfile instead of the default."
|
||||
method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely"
|
||||
method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache."
|
||||
method_option "no-lock", type: :boolean, banner: "Don't create a lockfile."
|
||||
method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed"
|
||||
method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)."
|
||||
method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)."
|
||||
method_option "quiet", type: :boolean, banner: "Only output warnings and errors."
|
||||
method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default, usually 'ruby' (removed)"
|
||||
method_option "standalone", type: :array, lazy_default: [], banner: "Make a bundle that can work without the Bundler runtime"
|
||||
method_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 (removed)"
|
||||
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("|")}"
|
||||
method_option "target-rbconfig", type: :string, banner: "Path to rbconfig.rb for the deployment target platform"
|
||||
method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group (removed)."
|
||||
method_option "with", type: :array, banner: "Include gems that are part of the specified named group (removed)."
|
||||
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
|
||||
def install
|
||||
%w[clean deployment frozen no-prune path shebang without with].each do |option|
|
||||
remembered_flag_deprecation(option)
|
||||
end
|
||||
|
||||
print_remembered_flag_deprecation("--system", "path.system", "true") if ARGV.include?("--system")
|
||||
|
||||
remembered_flag_deprecation("deployment", negative: true)
|
||||
|
||||
if ARGV.include?("--binstubs")
|
||||
removed_message = "The --binstubs option has been removed in favor of `bundle binstubs --all`"
|
||||
raise InvalidOption, removed_message
|
||||
end
|
||||
|
||||
require_relative "cli/install"
|
||||
options = self.options.dup
|
||||
options["lockfile"] ||= ENV["BUNDLE_LOCKFILE"]
|
||||
Bundler.settings.temporary(no_install: false) do
|
||||
Install.new(options).run
|
||||
end
|
||||
rescue GemfileNotFound => error
|
||||
invoke_other_command("cli_help")
|
||||
raise error # re-raise to show the error and get a failing exit status
|
||||
end
|
||||
|
||||
map aliases_for("install")
|
||||
|
||||
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 "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed"
|
||||
method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock"
|
||||
method_option "bundler", type: :string, lazy_default: ">= #{Bundler::VERSION}", 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 "pre", type: :boolean, banner: "Always choose the highest allowed version when updating gems, regardless of prerelease status"
|
||||
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."
|
||||
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
|
||||
def update(*gems)
|
||||
require_relative "cli/update"
|
||||
Bundler.settings.temporary(no_install: false) do
|
||||
Update.new(options, gems).run
|
||||
end
|
||||
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 (removed)."
|
||||
def show(gem_name = nil)
|
||||
if ARGV.include?("--outdated")
|
||||
removed_message = "the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`"
|
||||
raise InvalidOption, removed_message
|
||||
end
|
||||
require_relative "cli/show"
|
||||
Show.new(options, gem_name).run
|
||||
end
|
||||
|
||||
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: :array, default: [], banner: "print gems from a given set of groups"
|
||||
method_option "without-group", type: :array, default: [], banner: "print all gems except from a given set of groups"
|
||||
method_option "format", type: :string, banner: "format output ('json' is the only supported format)"
|
||||
method_option "paths", type: :boolean, banner: "print the path to each gem in the bundle"
|
||||
def list
|
||||
require_relative "cli/list"
|
||||
List.new(options).run
|
||||
end
|
||||
|
||||
map aliases_for("list")
|
||||
|
||||
desc "info GEM [OPTIONS]", "Show information for the given gem"
|
||||
method_option "path", type: :boolean, banner: "Print full path to gem"
|
||||
method_option "version", type: :boolean, banner: "Print gem version"
|
||||
def info(gem_name)
|
||||
require_relative "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, `bin` by default (removed)"
|
||||
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"
|
||||
method_option "all-platforms", type: :boolean, default: false, banner: "Install binstubs for all platforms"
|
||||
def binstubs(*gems)
|
||||
remembered_flag_deprecation("path", option_name: "bin")
|
||||
|
||||
require_relative "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 "require", aliases: "-r", type: :string, banner: "Adds require path to gem. Provide false, or a path as a string."
|
||||
method_option "path", type: :string
|
||||
method_option "git", type: :string
|
||||
method_option "github", type: :string
|
||||
method_option "branch", type: :string
|
||||
method_option "ref", type: :string
|
||||
method_option "glob", type: :string, banner: "The location of a dependency's .gemspec, expanded within Ruby (single quotes recommended)"
|
||||
method_option "quiet", type: :boolean, banner: "Only output warnings and errors."
|
||||
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"
|
||||
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
|
||||
def add(*gems)
|
||||
require_relative "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,
|
||||
--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 "filter-strict", type: :boolean, aliases: "--strict", 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"
|
||||
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
|
||||
def outdated(*gems)
|
||||
require_relative "cli/outdated"
|
||||
Outdated.new(options, gems).run
|
||||
end
|
||||
|
||||
desc "fund [OPTIONS]", "Lists information about gems seeking funding assistance"
|
||||
method_option "group", aliases: "-g", type: :array, banner: "Fetch funding information for a specific group"
|
||||
def fund
|
||||
require_relative "cli/fund"
|
||||
Fund.new(options).run
|
||||
end
|
||||
|
||||
desc "cache [OPTIONS]", "Locks and then caches all of the gems into vendor/cache"
|
||||
method_option "all", type: :boolean, default: Bundler.settings[:cache_all], banner: "Include all sources (including path and git) (removed)."
|
||||
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 update the cache."
|
||||
method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)."
|
||||
method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)."
|
||||
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 bundle cache operation's install (removed)"
|
||||
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
|
||||
long_desc <<-D
|
||||
The cache 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 cache
|
||||
print_remembered_flag_deprecation("--all", "cache_all", "true") if ARGV.include?("--all")
|
||||
print_remembered_flag_deprecation("--no-all", "cache_all", "false") if ARGV.include?("--no-all")
|
||||
|
||||
%w[frozen no-prune].each do |option|
|
||||
remembered_flag_deprecation(option)
|
||||
end
|
||||
|
||||
if flag_passed?("--path")
|
||||
removed_message =
|
||||
"The `--path` flag has been removed because its semantics were unclear. " \
|
||||
"Use `bundle config cache_path` to configure the path of your cache of gems, " \
|
||||
"and `bundle config path` to configure the path where your gems are installed, " \
|
||||
"and stop using this flag"
|
||||
raise InvalidOption, removed_message
|
||||
end
|
||||
|
||||
require_relative "cli/cache"
|
||||
Cache.new(options).run
|
||||
end
|
||||
|
||||
map aliases_for("cache")
|
||||
|
||||
desc "exec [OPTIONS]", "Run the command in context of the bundle"
|
||||
method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is not permitted (removed)."
|
||||
method_option :gemfile, type: :string, required: false, banner: "Use the specified gemfile instead of Gemfile"
|
||||
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
|
||||
def exec(*args)
|
||||
if ARGV.include?("--no-keep-file-descriptors")
|
||||
removed_message = "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to"
|
||||
raise InvalidOption, removed_message
|
||||
end
|
||||
|
||||
require_relative "cli/exec"
|
||||
Exec.new(options, args).run
|
||||
end
|
||||
|
||||
map aliases_for("exec")
|
||||
|
||||
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 superseded by local configuration, this command
|
||||
will show the current value, as well as any superseded values and
|
||||
where they were specified.
|
||||
D
|
||||
require_relative "cli/config"
|
||||
subcommand "config", Config
|
||||
|
||||
desc "open GEM", "Opens the source directory of the given bundled gem"
|
||||
method_option "path", type: :string, lazy_default: "", banner: "Open relative path of the gem source."
|
||||
def open(name)
|
||||
require_relative "cli/open"
|
||||
Open.new(options, name).run
|
||||
end
|
||||
|
||||
desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded"
|
||||
def console(group = nil)
|
||||
require_relative "cli/console"
|
||||
Console.new(options, group).run
|
||||
end
|
||||
|
||||
desc "version", "Prints Bundler version information"
|
||||
def version
|
||||
cli_help = current_command.name == "cli_help"
|
||||
if cli_help || ARGV.include?("version")
|
||||
build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})"
|
||||
end
|
||||
|
||||
if !cli_help
|
||||
Bundler.ui.info "#{Bundler.verbose_version}#{build_info}"
|
||||
else
|
||||
Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}"
|
||||
end
|
||||
end
|
||||
|
||||
map aliases_for("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
|
||||
|
||||
desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true
|
||||
def viz
|
||||
SharedHelpers.feature_removed! "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph"
|
||||
end
|
||||
|
||||
desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem"
|
||||
method_option :exe, type: :boolean, default: false, aliases: ["--bin", "-b"], banner: "Generate a binary executable for your library."
|
||||
method_option :coc, type: :boolean, banner: "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`."
|
||||
method_option :edit, type: :string, aliases: "-e", required: false, lazy_default: [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, banner: "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)"
|
||||
method_option :ext, type: :string, banner: "Generate the boilerplate for C extension code.", enum: EXTENSIONS
|
||||
method_option :git, type: :boolean, default: true, banner: "Initialize a git repo inside your library."
|
||||
method_option :mit, type: :boolean, banner: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`."
|
||||
method_option :rubocop, type: :boolean, banner: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true` (removed)."
|
||||
method_option :changelog, type: :boolean, banner: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`."
|
||||
method_option :test, type: :string, lazy_default: Bundler.settings["gem.test"] || "", aliases: "-t", banner: "Use the specified test framework for your library", enum: %w[rspec minitest test-unit], desc: "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`."
|
||||
method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], banner: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`"
|
||||
method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", enum: %w[rubocop standard], banner: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`"
|
||||
method_option :github_username, type: :string, default: Bundler.settings["gem.github_username"], banner: "Set your username on GitHub", desc: "Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username <your_username>`."
|
||||
method_option :bundle, type: :boolean, default: Bundler.settings["gem.bundle"], banner: "Automatically run `bundle install` after creation. Set a default with `bundle config set --global gem.bundle true`"
|
||||
|
||||
def gem(name)
|
||||
require_relative "cli/gem"
|
||||
|
||||
raise InvalidOption, "--rubocop has been removed, use --linter=rubocop" if ARGV.include?("--rubocop")
|
||||
raise InvalidOption, "--no-rubocop has been removed, use --no-linter" if ARGV.include?("--no-rubocop")
|
||||
|
||||
cmd_args = args + [self]
|
||||
cmd_args.unshift(options)
|
||||
|
||||
Gem.new(*cmd_args).run
|
||||
end
|
||||
|
||||
def self.source_root
|
||||
File.expand_path("templates", __dir__)
|
||||
end
|
||||
|
||||
desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory"
|
||||
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 cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application."
|
||||
def clean
|
||||
require_relative "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_relative "cli/platform"
|
||||
Platform.new(options).run
|
||||
end
|
||||
|
||||
desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", hide: true
|
||||
def inject(*)
|
||||
SharedHelpers.feature_removed! "The `inject` command has been replaced by the `add` command"
|
||||
end
|
||||
|
||||
desc "lock", "Creates a lockfile without installing"
|
||||
method_option "update", type: :array, lazy_default: true, repeatable: 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 "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile"
|
||||
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-checksums", type: :boolean, default: false, banner: "Adds checksums to the lockfile"
|
||||
method_option "add-platform", type: :array, default: [], repeatable: true, banner: "Add a new platform to the lockfile"
|
||||
method_option "remove-platform", type: :array, default: [], repeatable: true, banner: "Remove a platform from the lockfile"
|
||||
method_option "normalize-platforms", type: :boolean, default: false, banner: "Normalize lockfile platforms"
|
||||
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 "pre", type: :boolean, banner: "If updating, always choose the highest allowed version, regardless of prerelease status"
|
||||
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"
|
||||
method_option "bundler", type: :string, lazy_default: ">= #{Bundler::VERSION}", banner: "Update the locked version of bundler"
|
||||
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
|
||||
def lock
|
||||
require_relative "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"
|
||||
require_relative "cli/doctor"
|
||||
subcommand("doctor", Doctor)
|
||||
|
||||
desc "issue", "Learn how to report an issue in Bundler"
|
||||
def issue
|
||||
require_relative "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_relative "cli/pristine"
|
||||
Bundler.settings.temporary(no_install: false) do
|
||||
Pristine.new(gems).run
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.settings[:plugins]
|
||||
require_relative "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 = (COMMAND_ALIASES.keys + COMMAND_ALIASES.values).flatten
|
||||
|
||||
help_flags = %w[--help -h]
|
||||
exec_commands = ["exec"] + COMMAND_ALIASES["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
|
||||
|
||||
def self.check_invalid_ext_option(arguments)
|
||||
# when invalid version of `--ext` is called
|
||||
if invalid_ext_value?(arguments)
|
||||
removed_message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension."
|
||||
raise InvalidOption, removed_message
|
||||
end
|
||||
end
|
||||
|
||||
def self.invalid_ext_value?(arguments)
|
||||
index = arguments.index("--ext")
|
||||
next_argument = arguments[index + 1]
|
||||
|
||||
# it is ok when --ext is followed with valid extension value
|
||||
# for example `bundle gem hello --ext c`
|
||||
return false if EXTENSIONS.include?(next_argument)
|
||||
|
||||
# invalid call when --ext is called with no value in last position
|
||||
# for example `bundle gem hello_gem --ext`
|
||||
return true if next_argument.nil?
|
||||
|
||||
# invalid call when --ext is followed by other parameter
|
||||
# for example `bundle gem --ext --no-ci hello_gem`
|
||||
return true if next_argument.start_with?("-")
|
||||
|
||||
# invalid call when --ext is followed by gem name
|
||||
# for example `bundle gem --ext hello_gem`
|
||||
return true if next_argument
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_command
|
||||
_, _, config = @_initializer
|
||||
config[:current_command]
|
||||
end
|
||||
|
||||
def invoke_other_command(name)
|
||||
_, _, config = @_initializer
|
||||
original_command = config[:current_command]
|
||||
command = self.class.all_commands[name]
|
||||
config[:current_command] = command
|
||||
send(name)
|
||||
ensure
|
||||
config[:current_command] = original_command
|
||||
end
|
||||
|
||||
def current_command=(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.verbose_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?
|
||||
|
||||
require_relative "vendored_uri"
|
||||
remote = Source::Rubygems::Remote.new(Gem::URI("https://rubygems.org"))
|
||||
cache_path = Bundler.user_cache.join("compact_index", remote.cache_slug)
|
||||
latest = Bundler::CompactIndexClient.new(cache_path).latest_version("bundler")
|
||||
return unless latest
|
||||
|
||||
current = Gem::Version.new(VERSION)
|
||||
return if current >= latest
|
||||
|
||||
Bundler.ui.warn \
|
||||
"The latest bundler is #{latest}, but you are currently running #{current}.\n" \
|
||||
"To update to the most recent version, run `bundle update --bundler`"
|
||||
rescue RuntimeError
|
||||
nil
|
||||
end
|
||||
|
||||
def remembered_flag_deprecation(name, negative: false, option_name: nil)
|
||||
option = current_command.options[name]
|
||||
flag_name = option.switch_name
|
||||
flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative
|
||||
return unless flag_passed?(flag_name)
|
||||
|
||||
value = options[name]
|
||||
value = value.join(" ").to_s if option.type == :array
|
||||
value = "'#{value}'" unless option.type == :boolean
|
||||
|
||||
print_remembered_flag_deprecation(flag_name, option_name || name.tr("-", "_"), value)
|
||||
end
|
||||
|
||||
def print_remembered_flag_deprecation(flag_name, option_name, option_value)
|
||||
removed_message =
|
||||
"The `#{flag_name}` flag has been removed because it relied on being " \
|
||||
"remembered across bundler invocations, which bundler no longer does. " \
|
||||
"Instead please use `bundle config set #{option_name} #{option_value}`, " \
|
||||
"and stop using this flag"
|
||||
raise InvalidOption, removed_message
|
||||
end
|
||||
|
||||
def flag_passed?(name)
|
||||
ARGV.any? {|arg| name == arg.split("=")[0] }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Add
|
||||
attr_reader :gems, :options, :version
|
||||
|
||||
def initialize(options, gems)
|
||||
@gems = gems
|
||||
@options = options
|
||||
@options[:group] = options[:group].split(",").map(&:strip) unless options[:group].nil?
|
||||
@version = options[:version].split(",").map(&:strip) unless options[:version].nil?
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "warn" if options[:quiet]
|
||||
|
||||
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
|
||||
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
|
||||
|
||||
validate_options!
|
||||
inject_dependencies
|
||||
perform_bundle_install unless options["skip-install"]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def perform_bundle_install
|
||||
Installer.install(Bundler.root, Bundler.definition)
|
||||
Bundler.load.cache if Bundler.app_cache.exist?
|
||||
end
|
||||
|
||||
def inject_dependencies
|
||||
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])
|
||||
end
|
||||
|
||||
def validate_options!
|
||||
raise InvalidOption, "You cannot specify `--git` and `--github` at the same time." if options["git"] && options["github"]
|
||||
|
||||
unless options["git"] || options["github"]
|
||||
raise InvalidOption, "You cannot specify `--branch` unless `--git` or `--github` is specified." if options["branch"]
|
||||
|
||||
raise InvalidOption, "You cannot specify `--ref` unless `--git` or `--github` is specified." if options["ref"]
|
||||
end
|
||||
|
||||
raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"]
|
||||
|
||||
raise InvalidOption, "You cannot 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.to_a.each do |v|
|
||||
raise InvalidOption, "Invalid gem requirement pattern '#{v}'" unless Gem::Requirement::PATTERN.match?(v.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# 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&.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,
|
||||
all_platforms: options["all-platforms"],
|
||||
}
|
||||
|
||||
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]
|
||||
if gem_name == "bundler"
|
||||
Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") unless options[:all]
|
||||
next
|
||||
end
|
||||
|
||||
Bundler.settings.temporary(path: Bundler.settings[:path] || Bundler.root) do
|
||||
installer.generate_standalone_bundler_executable_stubs(spec, installer_opts)
|
||||
end
|
||||
else
|
||||
installer.generate_bundler_executable_stubs(spec, installer_opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Cache
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "warn" if options[:quiet]
|
||||
Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"]
|
||||
|
||||
install
|
||||
|
||||
Bundler.settings.temporary(cache_all_platforms: options["all-platforms"]) do
|
||||
Bundler.load.cache
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def install
|
||||
require_relative "install"
|
||||
options = self.options.dup
|
||||
options["local"] = false if Bundler.settings[:cache_all_platforms]
|
||||
options["no-cache"] = true
|
||||
Bundler::CLI::Install.new(options).run
|
||||
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]
|
||||
|
||||
definition = Bundler.definition
|
||||
definition.ensure_equivalent_gemfile_and_lockfile
|
||||
definition.validate_runtime!
|
||||
|
||||
begin
|
||||
definition.check!
|
||||
not_installed = definition.missing_specs
|
||||
rescue GemNotFound, GitError, SolveFailure
|
||||
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
|
||||
else
|
||||
definition.lock(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,172 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module CLI::Common
|
||||
def self.validate_cooldown!(value)
|
||||
return if value.nil?
|
||||
return if value.is_a?(Integer) && value >= 0
|
||||
raise InvalidOption, "Expected `--cooldown` to be a non-negative integer, got #{value.inspect}"
|
||||
end
|
||||
|
||||
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_cooldown_skipped_summary(definition = Bundler.definition)
|
||||
skipped = definition.cooldown_skipped
|
||||
return if skipped.empty?
|
||||
|
||||
Bundler.ui.info "The following gem versions were skipped by the cooldown setting:"
|
||||
skipped.each do |entry|
|
||||
days = entry[:available_in_days]
|
||||
Bundler.ui.info " * #{entry[:name]} #{entry[:version]} (available in #{days} #{days == 1 ? "day" : "days"}), resolved #{entry[:resolved]} instead"
|
||||
end
|
||||
end
|
||||
|
||||
def self.output_fund_metadata_summary
|
||||
return if Bundler.settings["ignore_funding_requests"]
|
||||
definition = Bundler.definition
|
||||
current_dependencies = definition.requested_dependencies
|
||||
current_specs = definition.specs
|
||||
|
||||
count = current_dependencies.count {|dep| current_specs[dep.name].first.metadata.key?("funding_uri") }
|
||||
|
||||
return if count.zero?
|
||||
|
||||
intro = count > 1 ? "#{count} installed gems you directly depend on are" : "#{count} installed gem you directly depend on is"
|
||||
message = "#{intro} looking for funding.\n Run `bundle fund` for details"
|
||||
Bundler.ui.info message
|
||||
end
|
||||
|
||||
def self.output_without_groups_message(command)
|
||||
return if Bundler.settings[:without].empty?
|
||||
Bundler.ui.confirm without_groups_message(command)
|
||||
end
|
||||
|
||||
def self.without_groups_message(command)
|
||||
command_in_past_tense = command == :install ? "installed" : "updated"
|
||||
groups = Bundler.settings[:without]
|
||||
"Gems in the #{verbalize_groups(groups)} were not #{command_in_past_tense}."
|
||||
end
|
||||
|
||||
def self.verbalize_groups(groups)
|
||||
groups.map! {|g| "'#{g}'" }
|
||||
group_list = [groups[0...-1].join(", "), groups[-1..-1]].
|
||||
reject {|s| s.to_s.empty? }.join(" and ")
|
||||
group_str = groups.size == 1 ? "group" : "groups"
|
||||
"#{group_str} #{group_list}"
|
||||
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.match?(regexp)
|
||||
end
|
||||
|
||||
default_spec = default_gem_spec(name)
|
||||
specs << default_spec if default_spec
|
||||
|
||||
case specs.count
|
||||
when 0
|
||||
dep_in_other_group = Bundler.definition.current_dependencies.find {|dep|dep.name == name }
|
||||
|
||||
if dep_in_other_group
|
||||
raise GemNotFound, "Could not find gem '#{name}', because it's in the #{verbalize_groups(dep_in_other_group.groups)}, configured to be ignored."
|
||||
else
|
||||
raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
|
||||
end
|
||||
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.default_gem_spec(name)
|
||||
gem_spec = Gem::Specification.find_all_by_name(name).last
|
||||
gem_spec if gem_spec&.default_gem?
|
||||
end
|
||||
|
||||
def self.ask_for_spec_from(specs)
|
||||
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)
|
||||
message = "Could not find gem '#{missing_gem_name}'."
|
||||
alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a }
|
||||
if alternate_names.include?(missing_gem_name.downcase)
|
||||
message += "\nDid you mean '#{missing_gem_name.downcase}'?"
|
||||
elsif defined?(DidYouMean::SpellChecker)
|
||||
suggestions = DidYouMean::SpellChecker.new(dictionary: alternate_names).correct(missing_gem_name)
|
||||
message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty?
|
||||
end
|
||||
message
|
||||
end
|
||||
|
||||
def self.ensure_all_gems_in_lockfile!(names, locked_gems = Bundler.locked_gems)
|
||||
return unless locked_gems
|
||||
|
||||
locked_names = locked_gems.specs.map(&:name).uniq
|
||||
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)
|
||||
patch_level << :patch if patch_level.empty? && Bundler.settings[:prefer_patch]
|
||||
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["filter-strict"]
|
||||
gvp.pre = options[:pre]
|
||||
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.bundler_5_mode? && Bundler.settings[:path].nil?
|
||||
clean &&= !Bundler.use_system_gems?
|
||||
clean
|
||||
end
|
||||
|
||||
def self.word_list(words)
|
||||
if words.empty?
|
||||
return ""
|
||||
end
|
||||
|
||||
words = words.map {|word| "'#{word}'" }
|
||||
|
||||
if words.length == 1
|
||||
return words[0]
|
||||
end
|
||||
|
||||
[words[0..-2].join(", "), words[-1]].join(" or ")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,208 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Config < Thor
|
||||
class_option :parseable, type: :boolean, banner: "Use minimal formatting for more parseable output"
|
||||
|
||||
def self.scope_options
|
||||
method_option :global, type: :boolean, banner: "Only change the global config"
|
||||
method_option :local, type: :boolean, banner: "Only change the local config"
|
||||
end
|
||||
private_class_method :scope_options
|
||||
|
||||
desc "base NAME [VALUE]", "The Bundler 1 config interface", hide: true
|
||||
scope_options
|
||||
method_option :delete, type: :boolean, banner: "delete"
|
||||
def base(name = nil, *value)
|
||||
new_args =
|
||||
if ARGV.size == 1
|
||||
["config", "list"]
|
||||
elsif ARGV.include?("--delete")
|
||||
ARGV.map {|arg| arg == "--delete" ? "unset" : arg }
|
||||
elsif ARGV.include?("--global") || ARGV.include?("--local") || ARGV.size == 3
|
||||
["config", "set", *ARGV[1..-1]]
|
||||
else
|
||||
["config", "get", ARGV[1]]
|
||||
end
|
||||
|
||||
message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead."
|
||||
SharedHelpers.feature_deprecated! message
|
||||
|
||||
Base.new(options, name, value, self).run
|
||||
end
|
||||
|
||||
desc "list", "List out all configured settings"
|
||||
def list
|
||||
Base.new(options, nil, nil, self).run
|
||||
end
|
||||
|
||||
desc "get NAME", "Returns the value for the given key"
|
||||
def get(name)
|
||||
Base.new(options, name, nil, self).run
|
||||
end
|
||||
|
||||
desc "set NAME VALUE", "Sets the given value for the given key"
|
||||
scope_options
|
||||
def set(name, value, *value_)
|
||||
Base.new(options, name, value_.unshift(value), self).run
|
||||
end
|
||||
|
||||
desc "unset NAME", "Unsets the value for the given key"
|
||||
scope_options
|
||||
def unset(name)
|
||||
options[:delete] = true
|
||||
Base.new(options, name, nil, self).run
|
||||
end
|
||||
|
||||
default_task :base
|
||||
|
||||
class Base
|
||||
attr_reader :name, :value, :options, :scope, :thor
|
||||
|
||||
def initialize(options, name, value, thor)
|
||||
@options = options
|
||||
@name = name
|
||||
value = Array(value)
|
||||
@value = value.empty? ? nil : value.join(" ")
|
||||
@thor = thor
|
||||
validate_scope!
|
||||
end
|
||||
|
||||
def run
|
||||
unless name
|
||||
warn_unused_scope "Ignoring --#{scope}"
|
||||
confirm_all
|
||||
return
|
||||
end
|
||||
|
||||
if options[:delete]
|
||||
if !explicit_scope? || scope != "global"
|
||||
Bundler.settings.set_local(name, nil)
|
||||
end
|
||||
if !explicit_scope? || scope != "local"
|
||||
Bundler.settings.set_global(name, nil)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if value.nil?
|
||||
warn_unused_scope "Ignoring --#{scope} since no value to set was given"
|
||||
current_value = Bundler.settings[name]
|
||||
|
||||
if options[:parseable]
|
||||
if value = Bundler.settings[name]
|
||||
Bundler.ui.info("#{name}=#{value}")
|
||||
end
|
||||
else
|
||||
confirm(name)
|
||||
end
|
||||
|
||||
if current_value.nil?
|
||||
exit 1
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
Bundler.ui.info(message) if message
|
||||
Bundler.settings.send("set_#{scope}", name, new_value)
|
||||
end
|
||||
|
||||
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(value)
|
||||
if name.start_with?("local.") && pathname.directory?
|
||||
pathname.expand_path.to_s
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def message
|
||||
locations = Bundler.settings.locations(name)
|
||||
if @options[:parseable]
|
||||
"#{name}=#{new_value}" if new_value
|
||||
elsif scope == "global"
|
||||
if !locations[:local].nil?
|
||||
"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].nil? && locations[:global] != value
|
||||
"You are replacing the current global value of #{name}, which is currently " \
|
||||
"#{locations[:global].inspect}"
|
||||
end
|
||||
elsif scope == "local" && !locations[:local].nil? && locations[:local] != value
|
||||
"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 explicit_scope?
|
||||
@explicit_scope
|
||||
end
|
||||
|
||||
def warn_unused_scope(msg)
|
||||
return unless explicit_scope?
|
||||
return if options[:parseable]
|
||||
|
||||
Bundler.ui.warn(msg)
|
||||
end
|
||||
|
||||
def validate_scope!
|
||||
@explicit_scope = true
|
||||
scopes = %w[global local].select {|s| options[s] }
|
||||
case scopes.size
|
||||
when 0
|
||||
@scope = inside_app? ? "local" : "global"
|
||||
@explicit_scope = false
|
||||
when 1
|
||||
@scope = scopes.first
|
||||
else
|
||||
raise InvalidOption,
|
||||
"The options #{scopes.join " and "} were specified. Please only use one of the switches at a time."
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inside_app?
|
||||
Bundler.root
|
||||
true
|
||||
rescue GemfileNotFound
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Console
|
||||
attr_reader :options, :group
|
||||
def initialize(options, group)
|
||||
@options = options
|
||||
@group = group
|
||||
end
|
||||
|
||||
def run
|
||||
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
|
||||
if name == "irb"
|
||||
if defined?(Gem::BUNDLED_GEMS) && Gem::BUNDLED_GEMS.respond_to?(:force_activate)
|
||||
Gem::BUNDLED_GEMS.force_activate "irb"
|
||||
require name
|
||||
return get_constant(name)
|
||||
end
|
||||
Bundler.ui.error "#{name} is not available"
|
||||
exit 1
|
||||
else
|
||||
Bundler.ui.error "Couldn't load console #{name}, falling back to irb"
|
||||
name = "irb"
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
def get_constant(name)
|
||||
const_name = {
|
||||
"pry" => :Pry,
|
||||
"ripl" => :Ripl,
|
||||
"irb" => :IRB,
|
||||
}[name]
|
||||
Object.const_get(const_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Doctor < Thor
|
||||
default_command(:diagnose)
|
||||
|
||||
desc "diagnose [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."
|
||||
method_option "ssl", type: :boolean, default: false, banner: "Diagnose SSL problems."
|
||||
def diagnose
|
||||
require_relative "doctor/diagnose"
|
||||
Diagnose.new(options).run
|
||||
end
|
||||
|
||||
desc "ssl [OPTIONS]", "Diagnose SSL problems"
|
||||
long_desc <<-D
|
||||
Diagnose SSL problems, especially related to certificates or TLS version while connecting to https://rubygems.org.
|
||||
D
|
||||
method_option "host", type: :string, banner: "The host to diagnose."
|
||||
method_option "tls-version", type: :string, banner: "Specify the SSL/TLS version when running the diagnostic. Accepts either <1.1> or <1.2>"
|
||||
method_option "verify-mode", type: :string, banner: "Specify the mode used for certification verification. Accepts either <peer> or <none>"
|
||||
def ssl
|
||||
require_relative "doctor/ssl"
|
||||
SSL.new(options).run
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,167 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rbconfig"
|
||||
require "shellwords"
|
||||
|
||||
module Bundler
|
||||
class CLI::Doctor::Diagnose
|
||||
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.shellescape}`.chomp
|
||||
dylibs = output.split("\n")[1..-1].filter_map {|l| l.match(DARWIN_REGEX)&.match(1) }.uniq
|
||||
# ignore @rpath and friends
|
||||
dylibs.reject {|dylib| dylib.start_with? "@" }
|
||||
end
|
||||
|
||||
def dylibs_ldd(path)
|
||||
output = `/usr/bin/ldd #{path.shellescape}`.chomp
|
||||
output.split("\n").filter_map do |l|
|
||||
match = l.match(LDD_REGEX)
|
||||
next if match.nil?
|
||||
match.captures[0]
|
||||
end
|
||||
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)
|
||||
SharedHelpers.glob_files_in_dir("**/*.bundle", spec.full_gem_path)
|
||||
end
|
||||
|
||||
def lookup_with_fiddle(path)
|
||||
require "fiddle"
|
||||
Fiddle.dlopen(path)
|
||||
false
|
||||
rescue Fiddle::DLError
|
||||
true
|
||||
end
|
||||
|
||||
def check!
|
||||
require_relative "../check"
|
||||
Bundler::CLI::Check.new({}).run
|
||||
end
|
||||
|
||||
def diagnose_ssl
|
||||
require_relative "ssl"
|
||||
Bundler::CLI::Doctor::SSL.new({}).run
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "warn" if options[:quiet]
|
||||
Bundler.settings.validate!
|
||||
check!
|
||||
diagnose_ssl if options[:ssl]
|
||||
|
||||
definition = Bundler.definition
|
||||
broken_links = {}
|
||||
|
||||
definition.specs.each do |spec|
|
||||
bundles_for_gem(spec).each do |bundle|
|
||||
bad_paths = dylibs(bundle).select do |f|
|
||||
lookup_with_fiddle(f)
|
||||
end
|
||||
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.flat_map do |spec, paths|
|
||||
paths.uniq.map do |path|
|
||||
"\n * #{spec.name}: #{path}"
|
||||
end
|
||||
end.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 = []
|
||||
files_not_readable_and_owned_by_different_user = []
|
||||
files_not_owned_by_current_user_but_still_readable = []
|
||||
broken_symlinks = []
|
||||
Find.find(Bundler.bundle_path.to_s).each do |f|
|
||||
if !File.exist?(f)
|
||||
broken_symlinks << f
|
||||
elsif !File.readable?(f)
|
||||
if File.stat(f).uid != Process.uid
|
||||
files_not_readable_and_owned_by_different_user << f
|
||||
else
|
||||
files_not_readable << f
|
||||
end
|
||||
elsif File.stat(f).uid != Process.uid
|
||||
files_not_owned_by_current_user_but_still_readable << f
|
||||
end
|
||||
end
|
||||
|
||||
ok = true
|
||||
|
||||
if broken_symlinks.any?
|
||||
Bundler.ui.warn "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{broken_symlinks.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
if files_not_owned_by_current_user_but_still_readable.any?
|
||||
Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \
|
||||
"user, but are still readable. These files are:\n - #{files_not_owned_by_current_user_but_still_readable.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
if files_not_readable_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. These files are:\n - #{files_not_readable_and_owned_by_different_user.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
if files_not_readable.any?
|
||||
Bundler.ui.warn "Files exist in the Bundler home that are not " \
|
||||
"readable by the current user. These files are:\n - #{files_not_readable.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,249 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rubygems/remote_fetcher"
|
||||
require "uri"
|
||||
|
||||
module Bundler
|
||||
class CLI::Doctor::SSL
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
return unless openssl_installed?
|
||||
|
||||
output_ssl_environment
|
||||
bundler_success = bundler_connection_successful?
|
||||
rubygem_success = rubygem_connection_successful?
|
||||
|
||||
return unless net_http_connection_successful?
|
||||
|
||||
Explanation.summarize(bundler_success, rubygem_success, host)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def host
|
||||
@options[:host] || "rubygems.org"
|
||||
end
|
||||
|
||||
def tls_version
|
||||
@options[:"tls-version"].then do |version|
|
||||
"TLS#{version.sub(".", "_")}".to_sym if version
|
||||
end
|
||||
end
|
||||
|
||||
def verify_mode
|
||||
mode = @options[:"verify-mode"] || :peer
|
||||
|
||||
@verify_mode ||= mode.then {|mod| OpenSSL::SSL.const_get("verify_#{mod}".upcase) }
|
||||
end
|
||||
|
||||
def uri
|
||||
@uri ||= URI("https://#{host}")
|
||||
end
|
||||
|
||||
def openssl_installed?
|
||||
require "openssl"
|
||||
|
||||
true
|
||||
rescue LoadError
|
||||
Bundler.ui.warn(<<~MSG)
|
||||
Oh no! Your Ruby doesn't have OpenSSL, so it can't connect to #{host}.
|
||||
You'll need to recompile or reinstall Ruby with OpenSSL support and try again.
|
||||
MSG
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def output_ssl_environment
|
||||
Bundler.ui.info(<<~MESSAGE)
|
||||
Here's your OpenSSL environment:
|
||||
|
||||
OpenSSL: #{OpenSSL::VERSION}
|
||||
Compiled with: #{OpenSSL::OPENSSL_VERSION}
|
||||
Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
|
||||
MESSAGE
|
||||
end
|
||||
|
||||
def bundler_connection_successful?
|
||||
Bundler.ui.info("\nTrying connections to #{uri}:\n")
|
||||
|
||||
bundler_uri = Gem::URI(uri.to_s)
|
||||
Bundler::Fetcher.new(
|
||||
Bundler::Source::Rubygems::Remote.new(bundler_uri)
|
||||
).send(:connection).request(bundler_uri)
|
||||
|
||||
Bundler.ui.info("Bundler: success")
|
||||
|
||||
true
|
||||
rescue StandardError => error
|
||||
Bundler.ui.warn("Bundler: failed (#{Explanation.explain_bundler_or_rubygems_error(error)})")
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def rubygem_connection_successful?
|
||||
Gem::RemoteFetcher.fetcher.fetch_path(uri)
|
||||
Bundler.ui.info("RubyGems: success")
|
||||
|
||||
true
|
||||
rescue StandardError => error
|
||||
Bundler.ui.warn("RubyGems: failed (#{Explanation.explain_bundler_or_rubygems_error(error)})")
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def net_http_connection_successful?
|
||||
::Gem::Net::HTTP.new(uri.host, uri.port).tap do |http|
|
||||
http.use_ssl = true
|
||||
http.min_version = tls_version
|
||||
http.max_version = tls_version
|
||||
http.verify_mode = verify_mode
|
||||
end.start
|
||||
|
||||
Bundler.ui.info("Ruby net/http: success")
|
||||
warn_on_unsupported_tls12
|
||||
|
||||
true
|
||||
rescue StandardError => error
|
||||
Bundler.ui.warn(<<~MSG)
|
||||
Ruby net/http: failed
|
||||
|
||||
Unfortunately, this Ruby can't connect to #{host}.
|
||||
|
||||
#{Explanation.explain_net_http_error(error, host, tls_version)}
|
||||
MSG
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def warn_on_unsupported_tls12
|
||||
ctx = OpenSSL::SSL::SSLContext.new
|
||||
supported = true
|
||||
|
||||
if ctx.respond_to?(:min_version=)
|
||||
begin
|
||||
ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
|
||||
rescue OpenSSL::SSL::SSLError, NameError
|
||||
supported = false
|
||||
end
|
||||
else
|
||||
supported = OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1_2) # rubocop:disable Naming/VariableNumber
|
||||
end
|
||||
|
||||
Bundler.ui.warn(<<~EOM) unless supported
|
||||
|
||||
WARNING: Although your Ruby can connect to #{host} today, your OpenSSL is very old!
|
||||
WARNING: You will need to upgrade OpenSSL to use #{host}.
|
||||
|
||||
EOM
|
||||
end
|
||||
|
||||
module Explanation
|
||||
extend self
|
||||
|
||||
def explain_bundler_or_rubygems_error(error)
|
||||
case error.message
|
||||
when /certificate verify failed/
|
||||
"certificate verification"
|
||||
when /read server hello A/
|
||||
"SSL/TLS protocol version mismatch"
|
||||
when /tlsv1 alert protocol version/
|
||||
"requested TLS version is too old"
|
||||
else
|
||||
error.message
|
||||
end
|
||||
end
|
||||
|
||||
def explain_net_http_error(error, host, tls_version)
|
||||
case error.message
|
||||
# Check for certificate errors
|
||||
when /certificate verify failed/
|
||||
<<~MSG
|
||||
#{show_ssl_certs}
|
||||
Your Ruby can't connect to #{host} because you are missing the certificate files OpenSSL needs to verify you are connecting to the genuine #{host} servers.
|
||||
MSG
|
||||
# Check for TLS version errors
|
||||
when /read server hello A/, /tlsv1 alert protocol version/
|
||||
if tls_version.to_s == "TLS1_3"
|
||||
"Your Ruby can't connect to #{host} because #{tls_version} isn't supported yet.\n"
|
||||
else
|
||||
<<~MSG
|
||||
Your Ruby can't connect to #{host} because your version of OpenSSL is too old.
|
||||
You'll need to upgrade your OpenSSL install and/or recompile Ruby to use a newer OpenSSL.
|
||||
MSG
|
||||
end
|
||||
# OpenSSL doesn't support TLS version specified by argument
|
||||
when /unknown SSL method/
|
||||
"Your Ruby can't connect because #{tls_version} isn't supported by your version of OpenSSL."
|
||||
else
|
||||
<<~MSG
|
||||
Even worse, we're not sure why.
|
||||
|
||||
Here's the full error information:
|
||||
#{error.class}: #{error.message}
|
||||
#{error.backtrace.join("\n ")}
|
||||
|
||||
You might have more luck using Mislav's SSL doctor.rb script. You can get it here:
|
||||
https://github.com/mislav/ssl-tools/blob/8b3dec4/doctor.rb
|
||||
|
||||
Read more about the script and how to use it in this blog post:
|
||||
https://mislav.net/2013/07/ruby-openssl/
|
||||
MSG
|
||||
end
|
||||
end
|
||||
|
||||
def summarize(bundler_success, rubygems_success, host)
|
||||
guide_url = "http://ruby.to/ssl-check-failed"
|
||||
|
||||
message = if bundler_success && rubygems_success
|
||||
<<~MSG
|
||||
Hooray! This Ruby can connect to #{host}.
|
||||
You are all set to use Bundler and RubyGems.
|
||||
|
||||
MSG
|
||||
elsif !bundler_success && !rubygems_success
|
||||
<<~MSG
|
||||
For some reason, your Ruby installation can connect to #{host}, but neither RubyGems nor Bundler can.
|
||||
The most likely fix is to manually upgrade RubyGems by following the instructions at #{guide_url}.
|
||||
After you've done that, run `gem install bundler` to upgrade Bundler, and then run this script again to make sure everything worked. ❣
|
||||
|
||||
MSG
|
||||
elsif !bundler_success
|
||||
<<~MSG
|
||||
Although your Ruby installation and RubyGems can both connect to #{host}, Bundler is having trouble.
|
||||
The most likely way to fix this is to upgrade Bundler by running `gem install bundler`.
|
||||
Run this script again after doing that to make sure everything is all set.
|
||||
If you're still having trouble, check out the troubleshooting guide at #{guide_url}.
|
||||
|
||||
MSG
|
||||
else
|
||||
<<~MSG
|
||||
It looks like Ruby and Bundler can connect to #{host}, but RubyGems itself cannot.
|
||||
You can likely solve this by manually downloading and installing a RubyGems update.
|
||||
Visit #{guide_url} for instructions on how to manually upgrade RubyGems.
|
||||
|
||||
MSG
|
||||
end
|
||||
|
||||
Bundler.ui.info("\n#{message}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def show_ssl_certs
|
||||
ssl_cert_file = ENV["SSL_CERT_FILE"] || OpenSSL::X509::DEFAULT_CERT_FILE
|
||||
ssl_cert_dir = ENV["SSL_CERT_DIR"] || OpenSSL::X509::DEFAULT_CERT_DIR
|
||||
|
||||
<<~MSG
|
||||
Below affect only Ruby net/http connections:
|
||||
SSL_CERT_FILE: #{File.exist?(ssl_cert_file) ? "exists #{ssl_cert_file}" : "is missing #{ssl_cert_file}"}
|
||||
SSL_CERT_DIR: #{Dir.exist?(ssl_cert_dir) ? "exists #{ssl_cert_dir}" : "is missing #{ssl_cert_dir}"}
|
||||
MSG
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,128 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../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
|
||||
@args << { close_others: !options.keep_file_descriptors? } unless Bundler.current_ruby.jruby?
|
||||
end
|
||||
|
||||
def run
|
||||
validate_cmd!
|
||||
SharedHelpers.set_bundle_environment
|
||||
if bin_path = Bundler.which(cmd)
|
||||
if !Bundler.settings[:disable_exec_load] && directly_loadable?(bin_path)
|
||||
bin_path.delete_suffix!(".bat") if Gem.win_platform?
|
||||
kernel_load(bin_path, *args)
|
||||
else
|
||||
bin_path = explicit_path(bin_path)
|
||||
kernel_exec(bin_path, *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)
|
||||
Kernel.exec(*args)
|
||||
rescue Errno::EACCES, Errno::ENOEXEC
|
||||
Bundler.ui.error "bundler: not executable: #{cmd}"
|
||||
exit 126
|
||||
rescue Errno::ENOENT
|
||||
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)
|
||||
require_relative "../setup"
|
||||
TRAPPED_SIGNALS.each {|s| trap(s, "DEFAULT") }
|
||||
Kernel.load(file)
|
||||
rescue SystemExit, SignalException
|
||||
raise
|
||||
rescue Exception # rubocop:disable Lint/RescueException
|
||||
Bundler.ui.error "bundler: failed to load command: #{cmd} (#{file})"
|
||||
Bundler::FriendlyErrors.disable!
|
||||
raise
|
||||
end
|
||||
|
||||
def process_title(file, args)
|
||||
"#{file} #{args.join(" ")}".strip
|
||||
end
|
||||
|
||||
def explicit_path(path)
|
||||
if File.absolute_path?(path) || explicit_relative_path?(path)
|
||||
path
|
||||
else
|
||||
".#{File::SEPARATOR}#{path}"
|
||||
end
|
||||
end
|
||||
|
||||
def explicit_relative_path?(path)
|
||||
[File::SEPARATOR, File::ALT_SEPARATOR].compact.any? do |separator|
|
||||
path.start_with?(".#{separator}", "..#{separator}")
|
||||
end
|
||||
end
|
||||
|
||||
def directly_loadable?(file)
|
||||
if Gem.win_platform?
|
||||
script_wrapper?(file)
|
||||
else
|
||||
ruby_shebang?(file)
|
||||
end
|
||||
end
|
||||
|
||||
def script_wrapper?(file)
|
||||
script_file = file.delete_suffix(".bat")
|
||||
return false unless File.exist?(script_file)
|
||||
|
||||
if File.zero?(script_file)
|
||||
Bundler.ui.warn "#{script_file} is empty"
|
||||
return false
|
||||
end
|
||||
|
||||
header = File.open(file, "r") {|f| f.read(32) }
|
||||
ruby_exe = "#{RbConfig::CONFIG["RUBY_INSTALL_NAME"]}#{RbConfig::CONFIG["EXEEXT"]}"
|
||||
ruby_exe = "ruby.exe" if ruby_exe.empty?
|
||||
header.include?(ruby_exe)
|
||||
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,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Fund
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.definition.validate_runtime!
|
||||
|
||||
groups = Array(options[:group]).map(&:to_sym)
|
||||
|
||||
deps = if groups.any?
|
||||
Bundler.definition.dependencies_for(groups)
|
||||
else
|
||||
Bundler.definition.requested_dependencies
|
||||
end
|
||||
|
||||
fund_info = deps.each_with_object([]) do |dep, arr|
|
||||
spec = Bundler.definition.specs[dep.name].first
|
||||
if spec.metadata.key?("funding_uri")
|
||||
arr << "* #{spec.name} (#{spec.version})\n Funding: #{spec.metadata["funding_uri"]}"
|
||||
end
|
||||
end
|
||||
|
||||
if fund_info.empty?
|
||||
Bundler.ui.info "None of the installed gems you directly depend on are looking for funding."
|
||||
else
|
||||
Bundler.ui.info fund_info.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,492 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI
|
||||
Bundler.require_thor_actions
|
||||
include Thor::Actions
|
||||
end
|
||||
|
||||
class CLI::Gem
|
||||
TEST_FRAMEWORK_VERSIONS = {
|
||||
"rspec" => "3.0",
|
||||
"minitest" => "5.16",
|
||||
"test-unit" => "3.0",
|
||||
}.freeze
|
||||
|
||||
DEFAULT_GITHUB_USERNAME = "[USERNAME]"
|
||||
|
||||
attr_reader :options, :gem_name, :thor, :name, :target, :extension
|
||||
|
||||
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 = Pathname.new(SharedHelpers.pwd).join(gem_name)
|
||||
|
||||
@extension = options[:ext]
|
||||
|
||||
validate_ext_name if @extension
|
||||
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("::")
|
||||
minitest_constant_name = constant_array.clone.tap {|a| a[-1] = "Test#{a[-1]}" }.join("::") # Foo::Bar => Foo::TestBar
|
||||
|
||||
use_git = Bundler.git_present? && options[:git]
|
||||
|
||||
git_author_name = use_git ? `git config user.name`.chomp : ""
|
||||
git_username = use_git ? `git config github.user`.chomp : ""
|
||||
git_user_email = use_git ? `git config user.email`.chomp : ""
|
||||
github_username = github_username(git_username)
|
||||
|
||||
if github_username.empty?
|
||||
homepage_uri = "TODO: Put your gem's website or public repo URL here."
|
||||
source_code_uri = "TODO: Put your gem's public repo URL here."
|
||||
changelog_uri = "TODO: Put your gem's CHANGELOG.md URL here."
|
||||
else
|
||||
homepage_uri = "https://github.com/#{github_username}/#{name}"
|
||||
source_code_uri = "https://github.com/#{github_username}/#{name}"
|
||||
changelog_uri = "https://github.com/#{github_username}/#{name}/blob/main/CHANGELOG.md"
|
||||
end
|
||||
|
||||
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: extension,
|
||||
exe: options[:exe],
|
||||
bundle: options[:bundle],
|
||||
bundler_version: bundler_dependency_version,
|
||||
git: use_git,
|
||||
github_username: github_username.empty? ? DEFAULT_GITHUB_USERNAME : github_username,
|
||||
required_ruby_version: required_ruby_version,
|
||||
rust_builder_required_rubygems_version: rust_builder_required_rubygems_version,
|
||||
minitest_constant_name: minitest_constant_name,
|
||||
ignore_paths: %w[bin/],
|
||||
homepage_uri: homepage_uri,
|
||||
source_code_uri: source_code_uri,
|
||||
changelog_uri: changelog_uri,
|
||||
}
|
||||
ensure_safe_gem_name(name, constant_array)
|
||||
|
||||
templates = {
|
||||
"Gemfile.tt" => Bundler.preferred_gemfile_name,
|
||||
"lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb",
|
||||
"lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb",
|
||||
"sig/newgem.rbs.tt" => "sig/#{namespaced_path}.rbs",
|
||||
"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
|
||||
]
|
||||
|
||||
case Bundler.preferred_gemfile_name
|
||||
when "Gemfile"
|
||||
config[:ignore_paths] << "Gemfile"
|
||||
when "gems.rb"
|
||||
config[:ignore_paths] << "gems.rb"
|
||||
config[:ignore_paths] << "gems.locked"
|
||||
end
|
||||
|
||||
if use_git
|
||||
templates.merge!("gitignore.tt" => ".gitignore")
|
||||
config[:ignore_paths] << ".gitignore"
|
||||
end
|
||||
|
||||
if test_framework = ask_and_set_test_framework
|
||||
config[:test] = test_framework
|
||||
config[:test_framework_version] = TEST_FRAMEWORK_VERSIONS[test_framework]
|
||||
|
||||
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"
|
||||
)
|
||||
config[:test_task] = :spec
|
||||
config[:ignore_paths] << ".rspec"
|
||||
config[:ignore_paths] << "spec/"
|
||||
when "minitest"
|
||||
# Generate path for minitest target file (FileList["test/**/test_*.rb"])
|
||||
# foo => test/test_foo.rb
|
||||
# foo-bar => test/foo/test_bar.rb
|
||||
# foo_bar => test/test_foo_bar.rb
|
||||
paths = namespaced_path.rpartition("/")
|
||||
paths[2] = "test_#{paths[2]}"
|
||||
minitest_namespaced_path = paths.join("")
|
||||
|
||||
templates.merge!(
|
||||
"test/minitest/test_helper.rb.tt" => "test/test_helper.rb",
|
||||
"test/minitest/test_newgem.rb.tt" => "test/#{minitest_namespaced_path}.rb"
|
||||
)
|
||||
config[:test_task] = :test
|
||||
config[:ignore_paths] << "test/"
|
||||
when "test-unit"
|
||||
templates.merge!(
|
||||
"test/test-unit/test_helper.rb.tt" => "test/test_helper.rb",
|
||||
"test/test-unit/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb"
|
||||
)
|
||||
config[:test_task] = :test
|
||||
config[:ignore_paths] << "test/"
|
||||
end
|
||||
end
|
||||
|
||||
config[:ci] = ask_and_set_ci
|
||||
case config[:ci]
|
||||
when "github"
|
||||
templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml")
|
||||
if extension == "rust"
|
||||
templates.merge!("github/workflows/build-gems.yml.tt" => ".github/workflows/build-gems.yml")
|
||||
end
|
||||
config[:ignore_paths] << ".github/"
|
||||
when "gitlab"
|
||||
templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml")
|
||||
config[:ignore_paths] << ".gitlab-ci.yml"
|
||||
when "circle"
|
||||
templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml")
|
||||
config[:ignore_paths] << ".circleci/"
|
||||
end
|
||||
|
||||
if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?",
|
||||
"Using a MIT license 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 safe, respectful, productive, and collaborative spaces. \n" \
|
||||
"See https://github.com/ruby/rubygems/blob/master/CODE_OF_CONDUCT.md")
|
||||
config[:coc] = true
|
||||
Bundler.ui.info "Code of conduct enabled in config"
|
||||
templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md")
|
||||
end
|
||||
|
||||
if ask_and_set(:changelog, "Do you want to include a changelog?",
|
||||
"A changelog is a file which contains a curated, chronologically ordered list of notable " \
|
||||
"changes for each version of a project. To make it easier for users and contributors to" \
|
||||
" see precisely what notable changes have been made between each release (or version) of" \
|
||||
" the project. Whether consumers or developers, the end users of software are" \
|
||||
" human beings who care about what's in the software. When the software changes, people " \
|
||||
"want to know why and how. see https://keepachangelog.com")
|
||||
config[:changelog] = true
|
||||
Bundler.ui.info "Changelog enabled in config"
|
||||
templates.merge!("CHANGELOG.md.tt" => "CHANGELOG.md")
|
||||
end
|
||||
|
||||
config[:linter] = ask_and_set_linter
|
||||
case config[:linter]
|
||||
when "rubocop"
|
||||
config[:linter_version] = rubocop_version
|
||||
Bundler.ui.info "RuboCop enabled in config"
|
||||
templates.merge!("rubocop.yml.tt" => ".rubocop.yml")
|
||||
config[:ignore_paths] << ".rubocop.yml"
|
||||
when "standard"
|
||||
config[:linter_version] = standard_version
|
||||
Bundler.ui.info "Standard enabled in config"
|
||||
templates.merge!("standard.yml.tt" => ".standard.yml")
|
||||
config[:ignore_paths] << ".standard.yml"
|
||||
end
|
||||
|
||||
if config[:exe]
|
||||
templates.merge!("exe/newgem.tt" => "exe/#{name}")
|
||||
executables.push("exe/#{name}")
|
||||
end
|
||||
|
||||
if extension == "c"
|
||||
templates.merge!(
|
||||
"ext/newgem/extconf-c.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
|
||||
|
||||
if extension == "rust"
|
||||
templates.merge!(
|
||||
"Cargo.toml.tt" => "Cargo.toml",
|
||||
"ext/newgem/Cargo.toml.tt" => "ext/#{name}/Cargo.toml",
|
||||
"ext/newgem/build.rs.tt" => "ext/#{name}/build.rs",
|
||||
"ext/newgem/extconf-rust.rb.tt" => "ext/#{name}/extconf.rb",
|
||||
"ext/newgem/src/lib.rs.tt" => "ext/#{name}/src/lib.rs",
|
||||
)
|
||||
end
|
||||
|
||||
if extension == "go"
|
||||
templates.merge!(
|
||||
"ext/newgem/go.mod.tt" => "ext/#{name}/go.mod",
|
||||
"ext/newgem/extconf-go.rb.tt" => "ext/#{name}/extconf.rb",
|
||||
"ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h",
|
||||
"ext/newgem/newgem.go.tt" => "ext/#{name}/#{underscored_name}.go",
|
||||
"ext/newgem/newgem-go.c.tt" => "ext/#{name}/#{underscored_name}.c",
|
||||
)
|
||||
|
||||
config[:go_module_username] = config[:github_username] == DEFAULT_GITHUB_USERNAME ? "username" : config[:github_username]
|
||||
end
|
||||
|
||||
if target.exist? && !target.directory?
|
||||
Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`."
|
||||
exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError]
|
||||
end
|
||||
|
||||
if use_git
|
||||
Bundler.ui.info "\nInitializing git repo in #{target}"
|
||||
IO.popen(["git", "init", target.to_s], &:read)
|
||||
|
||||
config[:git_default_branch] = File.read("#{target}/.git/HEAD").split("/").last.chomp
|
||||
end
|
||||
|
||||
templates.each do |src, dst|
|
||||
destination = target.join(dst)
|
||||
thor.template("newgem/#{src}", destination, config)
|
||||
end
|
||||
|
||||
executables.each do |file|
|
||||
path = target.join(file)
|
||||
executable = (path.stat.mode | 0o111)
|
||||
path.chmod(executable)
|
||||
end
|
||||
|
||||
if use_git
|
||||
IO.popen(%w[git add .], { chdir: target }, &:read)
|
||||
end
|
||||
|
||||
if config[:bundle]
|
||||
Bundler.ui.info "Running bundle install in the new gem directory."
|
||||
Dir.chdir(target) do
|
||||
system("bundle install")
|
||||
end
|
||||
end
|
||||
|
||||
# Open gemspec in editor
|
||||
open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit]
|
||||
|
||||
Bundler.ui.info "\nGem '#{name}' was successfully created. " \
|
||||
"For more information on making a RubyGem visit https://guides.rubygems.org/make-your-own-gem/"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolve_name(name)
|
||||
Pathname.new(SharedHelpers.pwd).join(name).basename.to_s
|
||||
end
|
||||
|
||||
def ask_and_set(key, prompt, explanation)
|
||||
choice = options[key]
|
||||
choice = Bundler.settings["gem.#{key}"] if choice.nil?
|
||||
|
||||
if choice.nil?
|
||||
Bundler.ui.info "\n#{explanation}"
|
||||
choice = Bundler.ui.yes? "#{prompt} 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" \
|
||||
"https://guides.rubygems.org/gems-with-extensions/\n"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def ask_and_set_test_framework
|
||||
return if skip?(:test)
|
||||
test_framework = options[:test] || Bundler.settings["gem.test"]
|
||||
|
||||
if test_framework.to_s.empty?
|
||||
Bundler.ui.info "\nDo you want to generate tests with your gem?"
|
||||
Bundler.ui.info hint_text("test")
|
||||
|
||||
result = Bundler.ui.ask "Enter a test framework. rspec/minitest/test-unit/(none):"
|
||||
if /rspec|minitest|test-unit/.match?(result)
|
||||
test_framework = result
|
||||
else
|
||||
test_framework = false
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.settings["gem.test"].nil?
|
||||
Bundler.settings.set_global("gem.test", test_framework)
|
||||
end
|
||||
|
||||
if options[:test] == Bundler.settings["gem.test"]
|
||||
Bundler.ui.info "#{options[:test]} is already configured, ignoring --test flag."
|
||||
end
|
||||
|
||||
test_framework
|
||||
end
|
||||
|
||||
def skip?(option)
|
||||
options.key?(option) && options[option].nil?
|
||||
end
|
||||
|
||||
def hint_text(setting)
|
||||
if Bundler.settings["gem.#{setting}"] == false
|
||||
"Your choice will only be applied to this gem."
|
||||
else
|
||||
"Future `bundle gem` calls will use your choice. " \
|
||||
"This setting can be changed anytime with `bundle config gem.#{setting}`."
|
||||
end
|
||||
end
|
||||
|
||||
def ask_and_set_ci
|
||||
return if skip?(:ci)
|
||||
ci_template = options[:ci] || Bundler.settings["gem.ci"]
|
||||
|
||||
if ci_template.to_s.empty?
|
||||
Bundler.ui.info "\nDo you want to set up continuous integration for your gem? " \
|
||||
"Supported services:\n" \
|
||||
"* CircleCI: https://circleci.com/\n" \
|
||||
"* GitHub Actions: https://github.com/features/actions\n" \
|
||||
"* GitLab CI: https://docs.gitlab.com/ee/ci/\n"
|
||||
Bundler.ui.info hint_text("ci")
|
||||
|
||||
result = Bundler.ui.ask "Enter a CI service. github/gitlab/circle/(none):"
|
||||
if /github|gitlab|circle/.match?(result)
|
||||
ci_template = result
|
||||
else
|
||||
ci_template = false
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.settings["gem.ci"].nil?
|
||||
Bundler.settings.set_global("gem.ci", ci_template)
|
||||
end
|
||||
|
||||
if options[:ci] == Bundler.settings["gem.ci"]
|
||||
Bundler.ui.info "#{options[:ci]} is already configured, ignoring --ci flag."
|
||||
end
|
||||
|
||||
ci_template
|
||||
end
|
||||
|
||||
def ask_and_set_linter
|
||||
return if skip?(:linter)
|
||||
linter_template = options[:linter] || Bundler.settings["gem.linter"]
|
||||
|
||||
if linter_template.to_s.empty?
|
||||
Bundler.ui.info "\nDo you want to add a code linter and formatter to your gem? " \
|
||||
"Supported Linters:\n" \
|
||||
"* RuboCop: https://rubocop.org\n" \
|
||||
"* Standard: https://github.com/standardrb/standard\n"
|
||||
Bundler.ui.info hint_text("linter")
|
||||
|
||||
result = Bundler.ui.ask "Enter a linter. rubocop/standard/(none):"
|
||||
if /rubocop|standard/.match?(result)
|
||||
linter_template = result
|
||||
else
|
||||
linter_template = false
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.settings["gem.linter"].nil?
|
||||
Bundler.settings.set_global("gem.linter", linter_template)
|
||||
end
|
||||
|
||||
# Once gem.linter safely set, unset the deprecated gem.rubocop
|
||||
unless Bundler.settings["gem.rubocop"].nil?
|
||||
Bundler.settings.set_global("gem.rubocop", nil)
|
||||
end
|
||||
|
||||
if options[:linter] == Bundler.settings["gem.linter"]
|
||||
Bundler.ui.info "#{options[:linter]} is already configured, ignoring --linter flag."
|
||||
end
|
||||
|
||||
linter_template
|
||||
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 /^\d/.match?(name)
|
||||
Bundler.ui.error "Invalid gem name #{name} Please give a name which does not start with numbers."
|
||||
exit 1
|
||||
end
|
||||
|
||||
if /[A-Z]/.match?(name)
|
||||
Bundler.ui.warn "Gem names with capital letters are not recommended. Please use only lowercase letters, numbers, and hyphens."
|
||||
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
|
||||
|
||||
def rust_builder_required_rubygems_version
|
||||
"3.3.11"
|
||||
end
|
||||
|
||||
def required_ruby_version
|
||||
"3.2.0"
|
||||
end
|
||||
|
||||
def rubocop_version
|
||||
"1.21"
|
||||
end
|
||||
|
||||
def standard_version
|
||||
"1.3"
|
||||
end
|
||||
|
||||
def github_username(git_username)
|
||||
if options[:github_username].nil?
|
||||
git_username
|
||||
elsif options[:github_username] == false
|
||||
""
|
||||
else
|
||||
options[:github_username]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
# 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
|
||||
Bundler.ui.silence do
|
||||
Bundler.definition.validate_runtime!
|
||||
Bundler.load.lock
|
||||
end
|
||||
|
||||
spec = spec_for_gem(gem_name)
|
||||
|
||||
if spec
|
||||
return print_gem_path(spec) if @options[:path]
|
||||
return print_gem_version(spec) if @options[:version]
|
||||
print_gem_info(spec)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def spec_for_gem(name)
|
||||
Bundler::CLI::Common.select_spec(name, :regex_match)
|
||||
end
|
||||
|
||||
def print_gem_version(spec)
|
||||
Bundler.ui.info spec.version.to_s
|
||||
end
|
||||
|
||||
def print_gem_path(spec)
|
||||
name = spec.name
|
||||
if name == "bundler"
|
||||
path = File.expand_path("../../..", __dir__)
|
||||
else
|
||||
path = spec.full_gem_path
|
||||
if spec.installation_missing?
|
||||
return Bundler.ui.warn "The gem #{name} is missing. It should be installed at #{path}, but was not found"
|
||||
end
|
||||
end
|
||||
|
||||
Bundler.ui.info path
|
||||
end
|
||||
|
||||
def print_gem_info(spec)
|
||||
metadata = spec.metadata
|
||||
name = spec.name
|
||||
gem_info = String.new
|
||||
gem_info << " * #{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 << "\tDocumentation: #{metadata["documentation_uri"]}\n" if metadata.key?("documentation_uri")
|
||||
gem_info << "\tSource Code: #{metadata["source_code_uri"]}\n" if metadata.key?("source_code_uri")
|
||||
gem_info << "\tFunding: #{metadata["funding_uri"]}\n" if metadata.key?("funding_uri")
|
||||
gem_info << "\tWiki: #{metadata["wiki_uri"]}\n" if metadata.key?("wiki_uri")
|
||||
gem_info << "\tChangelog: #{metadata["changelog_uri"]}\n" if metadata.key?("changelog_uri")
|
||||
gem_info << "\tBug Tracker: #{metadata["bug_tracker_uri"]}\n" if metadata.key?("bug_tracker_uri")
|
||||
gem_info << "\tMailing List: #{metadata["mailing_list_uri"]}\n" if metadata.key?("mailing_list_uri")
|
||||
gem_info << "\tPath: #{spec.full_gem_path}\n"
|
||||
gem_info << "\tDefault Gem: yes\n" if spec.respond_to?(:default_gem?) && spec.default_gem?
|
||||
gem_info << "\tReverse Dependencies: \n\t\t#{gem_dependencies.join("\n\t\t")}" if gem_dependencies.any?
|
||||
|
||||
if name != "bundler" && spec.installation_missing?
|
||||
return Bundler.ui.warn "The gem #{name} is missing. Gemspec information is still available though:\n#{gem_info}"
|
||||
end
|
||||
|
||||
Bundler.ui.info gem_info
|
||||
end
|
||||
|
||||
def gem_dependencies
|
||||
@gem_dependencies ||= Bundler.definition.specs.filter_map do |spec|
|
||||
dependency = spec.dependencies.find {|dep| dep.name == gem_name }
|
||||
next unless dependency
|
||||
"#{spec.name} (#{spec.version}) depends on #{gem_name} (#{dependency.requirements_list.join(", ")})"
|
||||
end.sort
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# 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)
|
||||
unless spec
|
||||
Bundler.ui.error "Gem specification #{gemspec} did not produce a specification"
|
||||
exit 1
|
||||
end
|
||||
|
||||
File.open(gemfile, "wb") do |file|
|
||||
file << "# Generated from #{gemspec}\n"
|
||||
file << spec.to_gemfile
|
||||
end
|
||||
else
|
||||
File.open(File.expand_path("../templates/Gemfile", __dir__), "r") do |template|
|
||||
File.open(gemfile, "wb") do |destination|
|
||||
IO.copy_stream(template, destination)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Writing new #{gemfile} to #{SharedHelpers.pwd}/#{gemfile}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gemfile
|
||||
@gemfile ||= options[:gemfile] || Bundler.preferred_gemfile_name
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,128 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Install
|
||||
attr_reader :options
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "warn" if options[:quiet]
|
||||
|
||||
warn_if_root
|
||||
|
||||
if options[:local]
|
||||
Bundler.self_manager.restart_with_locked_bundler_if_needed
|
||||
else
|
||||
Bundler.self_manager.install_locked_bundler_and_restart_with_it_if_needed
|
||||
end
|
||||
|
||||
Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Gem.freebsd_platform?
|
||||
|
||||
if target_rbconfig_path = options[:"target-rbconfig"]
|
||||
Bundler.rubygems.set_target_rbconfig(target_rbconfig_path)
|
||||
end
|
||||
|
||||
check_trust_policy
|
||||
|
||||
if Bundler.frozen_bundle? && !Bundler.default_lockfile.exist?
|
||||
flag = "deployment setting" if Bundler.settings[:deployment]
|
||||
flag = "frozen setting" if Bundler.settings[:frozen]
|
||||
raise ProductionError, "The #{flag} requires a lockfile. Please make " \
|
||||
"sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \
|
||||
"before deploying."
|
||||
end
|
||||
|
||||
normalize_settings
|
||||
|
||||
Bundler::Fetcher.disable_endpoint = options["full-index"]
|
||||
|
||||
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins]
|
||||
|
||||
# For install we want to enable strict validation
|
||||
# (rather than some optimizations we perform at app runtime).
|
||||
definition = Bundler.definition(strict: true)
|
||||
definition.validate_runtime!
|
||||
definition.lockfile = options["lockfile"] if options["lockfile"]
|
||||
definition.lockfile = false if options["no-lock"]
|
||||
|
||||
installer = Installer.install(Bundler.root, definition, options)
|
||||
|
||||
Bundler.settings.temporary(cache_all_platforms: options[:local] ? false : Bundler.settings[:cache_all_platforms]) do
|
||||
Bundler.load.cache(nil, options[:local]) if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.frozen_bundle?
|
||||
end
|
||||
|
||||
Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}."
|
||||
Bundler::CLI::Common.output_without_groups_message(:install)
|
||||
|
||||
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
|
||||
Bundler::CLI::Common.output_cooldown_skipped_summary(definition)
|
||||
|
||||
if CLI::Common.clean_after_install?
|
||||
require_relative "clean"
|
||||
Bundler::CLI::Clean.new(options).run
|
||||
end
|
||||
|
||||
Bundler::CLI::Common.output_fund_metadata_summary
|
||||
rescue Gem::InvalidSpecificationException
|
||||
Bundler.ui.warn "You have one or more invalid gemspecs that need to be fixed."
|
||||
raise
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def warn_if_root
|
||||
return if Bundler.settings[:silence_root_warning] || Gem.win_platform? || !Process.uid.zero?
|
||||
Bundler.ui.warn "Don't run Bundler as root. 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 {|spec| spec.name != "bundler" }
|
||||
"#{count} #{count == 1 ? "gem" : "gems"} now installed"
|
||||
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_settings
|
||||
if options["standalone"] && Bundler.settings[:path].nil? && !options["local"]
|
||||
Bundler.settings.set_command_option :path, "bundle"
|
||||
end
|
||||
|
||||
Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :jobs, options["jobs"]
|
||||
|
||||
Bundler::CLI::Common.validate_cooldown!(options["cooldown"])
|
||||
Bundler.settings.set_command_option_if_given :cooldown, options["cooldown"]
|
||||
|
||||
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"]
|
||||
|
||||
options[:force] = options[:redownload] if options[:redownload]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rbconfig"
|
||||
|
||||
module Bundler
|
||||
class CLI::Issue
|
||||
def run
|
||||
Bundler.ui.info <<~EOS
|
||||
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/ruby/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md
|
||||
|
||||
2. Instructions for common Bundler uses can be found on the documentation
|
||||
site: https://guides.rubygems.org/
|
||||
|
||||
3. Information about each Bundler command can be found in the Bundler
|
||||
man pages: https://guides.rubygems.org/command-reference/bundle/
|
||||
|
||||
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, by filling
|
||||
in the new issue form located at
|
||||
https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md,
|
||||
and copy and pasting the information below.
|
||||
|
||||
EOS
|
||||
|
||||
Bundler.ui.info Bundler::Env.report
|
||||
|
||||
Bundler.ui.info "\n## Bundle Doctor"
|
||||
doctor
|
||||
end
|
||||
|
||||
def doctor
|
||||
require_relative "doctor/diagnose"
|
||||
Bundler::CLI::Doctor::Diagnose.new({}).run
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,97 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "json"
|
||||
|
||||
module Bundler
|
||||
class CLI::List
|
||||
def initialize(options)
|
||||
@options = options
|
||||
@without_group = options["without-group"].map(&:to_sym)
|
||||
@only_group = options["only-group"].map(&:to_sym)
|
||||
@format = options["format"]
|
||||
end
|
||||
|
||||
def run
|
||||
raise InvalidOption, "The `--only-group` and `--without-group` options cannot be used together" if @only_group.any? && @without_group.any?
|
||||
|
||||
raise InvalidOption, "The `--name-only` and `--paths` options cannot be used together" if @options["name-only"] && @options[:paths]
|
||||
|
||||
specs = if @only_group.any? || @without_group.any?
|
||||
filtered_specs_by_groups
|
||||
else
|
||||
begin
|
||||
Bundler.load.specs
|
||||
rescue GemNotFound => e
|
||||
Bundler.ui.error e.message
|
||||
Bundler.ui.warn "Install missing gems with `bundle install`."
|
||||
exit 1
|
||||
end
|
||||
end.reject {|s| s.name == "bundler" }.sort_by(&:name)
|
||||
|
||||
case @format
|
||||
when "json"
|
||||
print_json(specs: specs)
|
||||
when nil
|
||||
print_human(specs: specs)
|
||||
else
|
||||
raise InvalidOption, "Unknown option`--format=#{@format}`. Supported formats: `json`"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def print_json(specs:)
|
||||
gems = if @options["name-only"]
|
||||
specs.map {|s| { name: s.name } }
|
||||
else
|
||||
specs.map do |s|
|
||||
{
|
||||
name: s.name,
|
||||
version: s.version.to_s,
|
||||
git_version: s.git_version&.strip,
|
||||
}.tap do |h|
|
||||
h[:path] = s.full_gem_path if @options["paths"]
|
||||
end
|
||||
end
|
||||
end
|
||||
Bundler.ui.info({ gems: gems }.to_json)
|
||||
end
|
||||
|
||||
def print_human(specs:)
|
||||
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
|
||||
|
||||
def verify_group_exists(groups)
|
||||
(@without_group + @only_group).each do |group|
|
||||
raise InvalidOption, "`#{group}` group could not be found." unless groups.include?(group)
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_specs_by_groups
|
||||
definition = Bundler.definition
|
||||
groups = definition.groups
|
||||
|
||||
verify_group_exists(groups)
|
||||
|
||||
show_groups =
|
||||
if @without_group.any?
|
||||
groups.reject {|g| @without_group.include?(g) }
|
||||
elsif @only_group.any?
|
||||
groups.select {|g| @only_group.include?(g) }
|
||||
else
|
||||
groups
|
||||
end.map(&:to_sym)
|
||||
|
||||
definition.specs_for(show_groups)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,102 @@
|
||||
# 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
|
||||
|
||||
check_for_conflicting_options
|
||||
|
||||
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
|
||||
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
|
||||
|
||||
print = options[:print]
|
||||
previous_output_stream = Bundler.ui.output_stream
|
||||
Bundler.ui.output_stream = :stderr if print
|
||||
|
||||
Bundler::Fetcher.disable_endpoint = options["full-index"]
|
||||
|
||||
update = options[:update]
|
||||
# --update is repeatable, so it parses as an array with one entry per
|
||||
# occurrence, where a bare `--update` produces a `true` entry
|
||||
update = update.include?(true) ? true : update.flatten if update.is_a?(Array)
|
||||
conservative = options[:conservative]
|
||||
bundler = options[:bundler]
|
||||
|
||||
if update.is_a?(Array) # unlocking specific gems
|
||||
Bundler::CLI::Common.ensure_all_gems_in_lockfile!(update)
|
||||
update = { gems: update, conservative: conservative }
|
||||
elsif update && conservative
|
||||
update = { conservative: conservative }
|
||||
elsif update && bundler
|
||||
update = { bundler: bundler }
|
||||
end
|
||||
|
||||
Bundler.settings.temporary(frozen: false) do
|
||||
definition = Bundler.definition(update, Bundler.default_lockfile)
|
||||
definition.add_checksums if options["add-checksums"]
|
||||
|
||||
Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update]
|
||||
|
||||
options["remove-platform"].flatten.each do |platform_string|
|
||||
platform = Gem::Platform.new(platform_string)
|
||||
definition.remove_platform(platform)
|
||||
end
|
||||
|
||||
options["add-platform"].flatten.each do |platform_string|
|
||||
platform = Gem::Platform.new(platform_string)
|
||||
if platform.to_s == "unknown"
|
||||
Bundler.ui.error "The platform `#{platform_string}` is unknown to RubyGems and can't be added to the lockfile."
|
||||
exit 1
|
||||
end
|
||||
definition.add_platform(platform)
|
||||
end
|
||||
|
||||
if definition.platforms.empty?
|
||||
raise InvalidOption, "Removing all platforms from the bundle is not allowed"
|
||||
end
|
||||
|
||||
definition.remotely! unless options[:local]
|
||||
|
||||
if options["normalize-platforms"]
|
||||
definition.normalize_platforms
|
||||
end
|
||||
|
||||
if print
|
||||
puts definition.to_lock
|
||||
else
|
||||
file = options[:lockfile]
|
||||
file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile
|
||||
|
||||
puts "Writing lockfile to #{file}"
|
||||
definition.write_lock(file, false)
|
||||
end
|
||||
|
||||
Bundler::CLI::Common.output_cooldown_skipped_summary(definition)
|
||||
end
|
||||
|
||||
Bundler.ui.output_stream = previous_output_stream
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_for_conflicting_options
|
||||
if options["normalize-platforms"] && options["add-platform"].any?
|
||||
raise InvalidOption, "--normalize-platforms can't be used with --add-platform"
|
||||
end
|
||||
|
||||
if options["normalize-platforms"] && options["remove-platform"].any?
|
||||
raise InvalidOption, "--normalize-platforms can't be used with --remove-platform"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Open
|
||||
attr_reader :options, :name, :path
|
||||
def initialize(options, name)
|
||||
@options = options
|
||||
@name = name
|
||||
@path = options[:path] unless options[:path].nil?
|
||||
end
|
||||
|
||||
def run
|
||||
raise InvalidOption, "Cannot specify `--path` option without a value" if !@path.nil? && @path.empty?
|
||||
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)
|
||||
if spec.default_gem?
|
||||
Bundler.ui.info "Unable to open #{name} because it's a default gem, so the directory it would normally be installed to does not exist."
|
||||
else
|
||||
root_path = spec.full_gem_path
|
||||
command = editor_command(editor) << File.join([root_path, path].compact)
|
||||
Bundler.with_original_env do
|
||||
system(*command, { chdir: root_path })
|
||||
end || Bundler.ui.info("Could not run '#{command.join(" ")}'")
|
||||
end
|
||||
end
|
||||
|
||||
def editor_command(editor)
|
||||
# On Windows an editor is often configured with a full path such as
|
||||
# C:\Program Files\Microsoft VS Code\Code.exe, which shell splitting
|
||||
# would corrupt. Take a value that names an existing file as a
|
||||
# single word.
|
||||
return [editor] if Gem.win_platform? && File.file?(editor)
|
||||
|
||||
require "shellwords"
|
||||
Shellwords.split(editor)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,367 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Outdated
|
||||
attr_reader :options, :gems, :options_include_groups, :filter_options_patch, :sources, :strict
|
||||
attr_accessor :outdated_gems
|
||||
|
||||
def initialize(options, gems)
|
||||
@options = options
|
||||
@gems = gems
|
||||
@sources = Array(options[:source])
|
||||
|
||||
@filter_options_patch = options.keys & %w[filter-major filter-minor filter-patch]
|
||||
|
||||
@outdated_gems = []
|
||||
|
||||
@options_include_groups = [:group, :groups].any? do |v|
|
||||
options.keys.include?(v.to_s)
|
||||
end
|
||||
|
||||
# the patch level options imply strict is also true. It wouldn't make
|
||||
# sense otherwise.
|
||||
@strict = options["filter-strict"] || Bundler::CLI::Common.patch_level_options(options).any?
|
||||
end
|
||||
|
||||
def run
|
||||
check_for_deployment_mode!
|
||||
|
||||
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
|
||||
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
|
||||
|
||||
Bundler.definition.validate_runtime!
|
||||
current_specs = Bundler.ui.silence { Bundler.definition.resolve }
|
||||
|
||||
gems.each do |gem_name|
|
||||
if current_specs[gem_name].empty?
|
||||
raise GemNotFound, "Could not find gem '#{gem_name}'."
|
||||
end
|
||||
end
|
||||
|
||||
current_dependencies = Bundler.ui.silence do
|
||||
Bundler.load.dependencies.map {|dep| [dep.name, dep] }.to_h
|
||||
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.merge(strict: @strict)
|
||||
)
|
||||
|
||||
definition_resolution = proc do
|
||||
options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely!
|
||||
end
|
||||
|
||||
if options[:parseable]
|
||||
Bundler.ui.progress(&definition_resolution)
|
||||
else
|
||||
definition_resolution.call
|
||||
end
|
||||
|
||||
Bundler.ui.info ""
|
||||
|
||||
# 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).uniq(&:name).each do |current_spec|
|
||||
next unless gems.empty? || gems.include?(current_spec.name)
|
||||
|
||||
active_spec = retrieve_active_spec(definition, current_spec)
|
||||
next unless active_spec
|
||||
|
||||
next unless filter_options_patch.empty? || update_present_via_semver_portions(current_spec, active_spec, options)
|
||||
|
||||
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)
|
||||
|
||||
dependency = current_dependencies[current_spec.name]
|
||||
groups = ""
|
||||
if dependency && !options[:parseable]
|
||||
groups = dependency.groups.join(", ")
|
||||
end
|
||||
|
||||
outdated_gems << {
|
||||
active_spec: active_spec,
|
||||
current_spec: current_spec,
|
||||
dependency: dependency,
|
||||
groups: groups,
|
||||
}
|
||||
end
|
||||
|
||||
relevant_outdated_gems = if options_include_groups
|
||||
outdated_gems.group_by {|g| g[:groups] }.sort.flat_map do |groups, gems|
|
||||
contains_group = groups.split(", ").include?(options[:group])
|
||||
next unless options[:groups] || contains_group
|
||||
|
||||
gems
|
||||
end.compact
|
||||
else
|
||||
outdated_gems
|
||||
end
|
||||
|
||||
if relevant_outdated_gems.empty?
|
||||
unless options[:parseable]
|
||||
Bundler.ui.info(nothing_outdated_message)
|
||||
end
|
||||
else
|
||||
if options[:parseable]
|
||||
print_gems(relevant_outdated_gems)
|
||||
else
|
||||
print_gems_table(relevant_outdated_gems)
|
||||
end
|
||||
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def loaded_from_for(spec)
|
||||
return unless spec.respond_to?(:loaded_from)
|
||||
|
||||
spec.loaded_from
|
||||
end
|
||||
|
||||
def groups_text(group_text, groups)
|
||||
"#{group_text}#{groups.split(",").size > 1 ? "s" : ""} \"#{groups}\""
|
||||
end
|
||||
|
||||
def nothing_outdated_message
|
||||
if filter_options_patch.any?
|
||||
display = filter_options_patch.map do |o|
|
||||
o.sub("filter-", "")
|
||||
end.join(" or ")
|
||||
|
||||
"No #{display} updates to display.\n"
|
||||
else
|
||||
"Bundle up to date!\n"
|
||||
end
|
||||
end
|
||||
|
||||
def retrieve_active_spec(definition, current_spec)
|
||||
active_spec = definition.resolve.find_by_name_and_platform(current_spec.name, current_spec.platform)
|
||||
return unless active_spec
|
||||
|
||||
return active_spec if strict
|
||||
|
||||
matching_specs(active_spec, current_spec).last
|
||||
end
|
||||
|
||||
def matching_specs(active_spec, current_spec)
|
||||
@matching_specs ||= {}
|
||||
@matching_specs[[active_spec.source, current_spec.name, current_spec.platform]] ||= begin
|
||||
active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version)
|
||||
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_specs
|
||||
end
|
||||
end
|
||||
|
||||
# The newest version the cooldown setting would let bundler adopt right
|
||||
# now, when the newest overall version is still inside the window. Only a
|
||||
# version strictly between the installed one and the newest one is an
|
||||
# adoptable update worth showing.
|
||||
def newest_out_of_cooldown(active_spec, current_spec)
|
||||
newest = matching_specs(active_spec, current_spec).reverse_each.find {|spec| cooldown_days_remaining(spec).nil? }
|
||||
return unless newest
|
||||
return if newest.version >= active_spec.version || newest.version <= current_spec.version
|
||||
newest
|
||||
end
|
||||
|
||||
def print_gems(gems_list)
|
||||
gems_list.each do |gem|
|
||||
print_gem(
|
||||
gem[:current_spec],
|
||||
gem[:active_spec],
|
||||
gem[:dependency],
|
||||
gem[:groups],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def print_gems_table(gems_list)
|
||||
data = gems_list.map do |gem|
|
||||
gem_column_for(
|
||||
gem[:current_spec],
|
||||
gem[:active_spec],
|
||||
gem[:dependency],
|
||||
gem[:groups],
|
||||
)
|
||||
end
|
||||
|
||||
print_indented([table_header] + data)
|
||||
end
|
||||
|
||||
def print_gem(current_spec, active_spec, dependency, groups)
|
||||
spec_version = "#{active_spec.version}#{active_spec.git_version}"
|
||||
if Bundler.ui.debug?
|
||||
loaded_from = loaded_from_for(active_spec)
|
||||
spec_version += " (from #{loaded_from})" if loaded_from
|
||||
end
|
||||
current_version = "#{current_spec.version}#{current_spec.git_version}"
|
||||
|
||||
if dependency&.specific?
|
||||
dependency_version = %(, requested #{dependency.requirement})
|
||||
end
|
||||
|
||||
spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, " \
|
||||
"installed #{current_version}#{dependency_version}"
|
||||
|
||||
release_date = release_date_for(active_spec)
|
||||
spec_outdated_info += ", released #{release_date}" unless release_date.empty?
|
||||
|
||||
remaining = cooldown_days_remaining(active_spec)
|
||||
if remaining
|
||||
spec_outdated_info += ", in cooldown for #{remaining} more day#{"s" if remaining > 1}"
|
||||
adoptable = newest_out_of_cooldown(active_spec, current_spec)
|
||||
spec_outdated_info += ", newest out of cooldown #{adoptable.version}" if adoptable
|
||||
end
|
||||
|
||||
spec_outdated_info += ")"
|
||||
|
||||
output_message = if options[:parseable]
|
||||
spec_outdated_info.to_s
|
||||
elsif options_include_groups || groups.empty?
|
||||
" * #{spec_outdated_info}"
|
||||
else
|
||||
" * #{spec_outdated_info} in #{groups_text("group", groups)}"
|
||||
end
|
||||
|
||||
Bundler.ui.info output_message.rstrip
|
||||
end
|
||||
|
||||
def gem_column_for(current_spec, active_spec, dependency, groups)
|
||||
current_version = "#{current_spec.version}#{current_spec.git_version}"
|
||||
spec_version = "#{active_spec.version}#{active_spec.git_version}"
|
||||
remaining = cooldown_days_remaining(active_spec)
|
||||
if remaining
|
||||
adoptable = newest_out_of_cooldown(active_spec, current_spec)
|
||||
adoptable_note = adoptable ? ", #{adoptable.version} out of cooldown" : ""
|
||||
spec_version += " (cooldown #{remaining}d#{adoptable_note})"
|
||||
end
|
||||
dependency = dependency.requirement if dependency
|
||||
|
||||
ret_val = [active_spec.name, current_version, spec_version, dependency.to_s, groups.to_s]
|
||||
ret_val << release_date_for(active_spec)
|
||||
ret_val << loaded_from_for(active_spec).to_s if Bundler.ui.debug?
|
||||
ret_val
|
||||
end
|
||||
|
||||
def cooldown_days_remaining(spec, now = cooldown_now)
|
||||
return nil unless spec.respond_to?(:created_at) && spec.created_at
|
||||
return nil unless spec.respond_to?(:remote) && spec.remote
|
||||
days = spec.remote.effective_cooldown
|
||||
return nil if days.nil? || days <= 0
|
||||
remaining = days - ((now - spec.created_at) / 86_400.0)
|
||||
remaining > 0 ? remaining.ceil : nil
|
||||
end
|
||||
|
||||
def cooldown_now
|
||||
@cooldown_now ||= Time.now
|
||||
end
|
||||
|
||||
def check_for_deployment_mode!
|
||||
return unless Bundler.frozen_bundle?
|
||||
suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any?
|
||||
"bundle config unset frozen"
|
||||
elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
|
||||
"bundle config unset 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.to_a[0].to_i
|
||||
end
|
||||
|
||||
def print_indented(matrix)
|
||||
header = matrix[0]
|
||||
data = matrix[1..-1]
|
||||
|
||||
column_sizes = Array.new(header.size) do |index|
|
||||
matrix.max_by {|row| row[index].length }[index].length
|
||||
end
|
||||
|
||||
Bundler.ui.info justify(header, column_sizes)
|
||||
|
||||
data.sort_by! {|row| row[0] }
|
||||
|
||||
data.each do |row|
|
||||
Bundler.ui.info justify(row, column_sizes)
|
||||
end
|
||||
end
|
||||
|
||||
def table_header
|
||||
header = ["Gem", "Current", "Latest", "Requested", "Groups", "Release Date"]
|
||||
header << "Path" if Bundler.ui.debug?
|
||||
header
|
||||
end
|
||||
|
||||
def release_date_for(spec)
|
||||
return "" unless spec.respond_to?(:date)
|
||||
|
||||
date = spec.date
|
||||
return "" unless date
|
||||
|
||||
return "" unless Gem.const_defined?(:DEFAULT_SOURCE_DATE_EPOCH)
|
||||
default_date = Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc
|
||||
default_date = Time.utc(default_date.year, default_date.month, default_date.day)
|
||||
|
||||
date = date.utc if date.respond_to?(:utc)
|
||||
|
||||
return "" if date == default_date
|
||||
|
||||
date.strftime("%Y-%m-%d")
|
||||
end
|
||||
|
||||
def justify(row, sizes)
|
||||
row.each_with_index.map do |element, index|
|
||||
element.ljust(sizes[index])
|
||||
end.join(" ").strip + "\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Platform
|
||||
attr_reader :options
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
ruby_version = if Bundler.locked_gems
|
||||
Bundler.locked_gems.ruby_version&.gsub(/p\d+\Z/, "")
|
||||
else
|
||||
Bundler.definition.ruby_version&.single_version_string
|
||||
end
|
||||
|
||||
output = []
|
||||
|
||||
if options[:ruby]
|
||||
if ruby_version
|
||||
output << ruby_version
|
||||
else
|
||||
output << "No ruby version specified"
|
||||
end
|
||||
else
|
||||
platforms = Bundler.definition.platforms.map {|p| "* #{p}" }
|
||||
|
||||
output << "Your platform is: #{Gem::Platform.local}"
|
||||
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,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../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), from a git source provided with --git, or a local path provided with --path. 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 "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (removed)"
|
||||
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"
|
||||
method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use"
|
||||
def install(*plugins)
|
||||
if options.key?(:local_git)
|
||||
raise InvalidOption, "--local_git has been removed, use --git"
|
||||
end
|
||||
|
||||
Bundler::Plugin.install(plugins, options)
|
||||
end
|
||||
|
||||
desc "uninstall PLUGINS", "Uninstall the plugins"
|
||||
long_desc <<-D
|
||||
Uninstall given list of plugins. To uninstall all the plugins, use -all option.
|
||||
D
|
||||
method_option "all", type: :boolean, default: nil, banner: "Uninstall all the installed plugins. If no plugin is installed, then it does nothing."
|
||||
def uninstall(*plugins)
|
||||
Bundler::Plugin.uninstall(plugins, options)
|
||||
end
|
||||
|
||||
desc "list", "List the installed plugins and available commands"
|
||||
def list
|
||||
Bundler::Plugin.list
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
# 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)
|
||||
git_sources = []
|
||||
|
||||
ProcessLock.lock do
|
||||
installed_specs = definition.specs.reject 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
|
||||
if source.local?
|
||||
Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is locally overridden.")
|
||||
next
|
||||
end
|
||||
|
||||
source.remote!
|
||||
if extension_cache_path = source.extension_cache_path(spec)
|
||||
FileUtils.rm_rf extension_cache_path
|
||||
end
|
||||
FileUtils.rm_rf spec.extension_dir
|
||||
FileUtils.rm_rf spec.full_gem_path
|
||||
|
||||
next if git_sources.include?(source)
|
||||
git_sources << source
|
||||
else
|
||||
Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.")
|
||||
next
|
||||
end
|
||||
|
||||
true
|
||||
end.map(&:name)
|
||||
|
||||
jobs = Bundler.settings.installation_parallelization
|
||||
pristine_count = definition.specs.count - installed_specs.count
|
||||
# allow a pristining a single gem to skip the parallel worker
|
||||
jobs = [jobs, pristine_count].min
|
||||
ParallelInstaller.call(installer, definition.specs, jobs, false, true, skip: installed_specs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
# 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]
|
||||
@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("../../..", __dir__)
|
||||
else
|
||||
spec = Bundler::CLI::Common.select_spec(gem_name, :regex_match)
|
||||
return unless spec
|
||||
path = spec.full_gem_path
|
||||
unless File.directory?(path)
|
||||
return Bundler.ui.warn "The gem #{gem_name} is missing. It should be installed at #{path}, but was not found"
|
||||
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
|
||||
#{desc.lstrip}
|
||||
\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)
|
||||
Bundler.ui.info "Fetching remote specs for outdated check...\n\n"
|
||||
Bundler.ui.silence { definition.remotely! }
|
||||
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,126 @@
|
||||
# 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 = "warn" if options[:quiet]
|
||||
|
||||
update_bundler = options[:bundler]
|
||||
|
||||
Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler, pre: options[:pre]) if update_bundler
|
||||
|
||||
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins]
|
||||
|
||||
sources = Array(options[:source])
|
||||
groups = Array(options[:group]).map(&:to_sym)
|
||||
|
||||
full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !update_bundler
|
||||
|
||||
if full_update && !options[:all]
|
||||
if Bundler.settings[:update_requires_all_flag]
|
||||
raise InvalidOption, "To update everything, pass the `--all` flag."
|
||||
end
|
||||
SharedHelpers.feature_deprecated! "Pass --all to `bundle update` to update everything"
|
||||
elsif !full_update && options[:all]
|
||||
raise InvalidOption, "Cannot specify --all along with specific options."
|
||||
end
|
||||
|
||||
conservative = options[:conservative]
|
||||
|
||||
if full_update
|
||||
if conservative
|
||||
Bundler.definition(conservative: conservative)
|
||||
else
|
||||
Bundler.definition(true)
|
||||
end
|
||||
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],
|
||||
conservative: conservative,
|
||||
bundler: update_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]
|
||||
opts["force"] = options[:redownload] if options[:redownload]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
|
||||
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
|
||||
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
|
||||
|
||||
Bundler.definition.validate_runtime!
|
||||
|
||||
if locked_gems = Bundler.definition.locked_gems
|
||||
previous_locked_info = locked_gems.specs.reduce({}) do |h, s|
|
||||
h[s.name] = { spec: s, version: s.version, source: s.source.identifier }
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
installer = Installer.install Bundler.root, Bundler.definition, opts
|
||||
Bundler.load.cache if Bundler.app_cache.exist?
|
||||
|
||||
if CLI::Common.clean_after_install?
|
||||
require_relative "clean"
|
||||
Bundler::CLI::Clean.new(options).run
|
||||
end
|
||||
|
||||
if locked_gems
|
||||
gems.each do |name|
|
||||
locked_info = previous_locked_info[name]
|
||||
next unless locked_info
|
||||
|
||||
locked_spec = locked_info[:spec]
|
||||
new_spec = Bundler.definition.specs[name].first
|
||||
unless new_spec
|
||||
unless locked_spec.installable_on_platform?(Bundler.local_platform)
|
||||
Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one"
|
||||
end
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
locked_source = locked_info[:source]
|
||||
new_source = new_spec.source.identifier
|
||||
next if locked_source != new_source
|
||||
|
||||
new_version = new_spec.version
|
||||
locked_version = locked_info[:version]
|
||||
if 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(:update)
|
||||
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
|
||||
Bundler::CLI::Common.output_cooldown_skipped_summary
|
||||
|
||||
Bundler::CLI::Common.output_fund_metadata_summary
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,92 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "set"
|
||||
|
||||
module Bundler
|
||||
# The CompactIndexClient is responsible for fetching and parsing the compact index.
|
||||
#
|
||||
# The compact index is a set of caching optimized files that are used to fetch gem information.
|
||||
# The files are:
|
||||
# - names: a list of all gem names
|
||||
# - versions: a list of all gem versions
|
||||
# - info/[gem]: a list of all versions of a gem
|
||||
#
|
||||
# The client is instantiated with:
|
||||
# - `directory`: the root directory where the cache files are stored.
|
||||
# - `fetcher`: (optional) an object that responds to #call(uri_path, headers) and returns an http response.
|
||||
# If the `fetcher` is not provided, the client will only read cached files from disk.
|
||||
#
|
||||
# The client is organized into:
|
||||
# - `Updater`: updates the cached files on disk using the fetcher.
|
||||
# - `Cache`: calls the updater, caches files, read and return them from disk
|
||||
# - `Parser`: parses the compact index file data
|
||||
# - `CacheFile`: a concurrency safe file reader/writer that verifies checksums
|
||||
#
|
||||
# The client is intended to optimize memory usage and performance.
|
||||
# It is called 100s or 1000s of times, parsing files with hundreds of thousands of lines.
|
||||
# It may be called concurrently without global interpreter lock in some Rubies.
|
||||
# As a result, some methods may look more complex than necessary to save memory or time.
|
||||
class CompactIndexClient
|
||||
SUPPORTED_DIGESTS = { "sha-256" => :SHA256 }.freeze
|
||||
DEBUG_MUTEX = Thread::Mutex.new
|
||||
|
||||
# info returns an Array of INFO Arrays. Each INFO Array has the following indices:
|
||||
INFO_NAME = 0
|
||||
INFO_VERSION = 1
|
||||
INFO_PLATFORM = 2
|
||||
INFO_DEPS = 3
|
||||
INFO_REQS = 4
|
||||
|
||||
def self.debug
|
||||
return unless ENV["DEBUG_COMPACT_INDEX"]
|
||||
DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
|
||||
end
|
||||
|
||||
class Error < StandardError; end
|
||||
|
||||
require_relative "compact_index_client/cache"
|
||||
require_relative "compact_index_client/cache_file"
|
||||
require_relative "compact_index_client/parser"
|
||||
require_relative "compact_index_client/updater"
|
||||
|
||||
def initialize(directory, fetcher = nil)
|
||||
@cache = Cache.new(directory, fetcher)
|
||||
@parser = Parser.new(@cache)
|
||||
end
|
||||
|
||||
def names
|
||||
Bundler::CompactIndexClient.debug { "names" }
|
||||
@parser.names
|
||||
end
|
||||
|
||||
def versions
|
||||
Bundler::CompactIndexClient.debug { "versions" }
|
||||
@parser.versions
|
||||
end
|
||||
|
||||
def dependencies(names)
|
||||
Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
|
||||
names.map {|name| info(name) }
|
||||
end
|
||||
|
||||
def info(name)
|
||||
Bundler::CompactIndexClient.debug { "info(#{name})" }
|
||||
@parser.info(name)
|
||||
end
|
||||
|
||||
def latest_version(name)
|
||||
Bundler::CompactIndexClient.debug { "latest_version(#{name})" }
|
||||
@parser.info(name).map {|d| Gem::Version.new(d[INFO_VERSION]) }.max
|
||||
end
|
||||
|
||||
def available?
|
||||
Bundler::CompactIndexClient.debug { "available?" }
|
||||
@parser.available?
|
||||
end
|
||||
|
||||
def reset!
|
||||
Bundler::CompactIndexClient.debug { "reset!" }
|
||||
@cache.reset!
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rubygems/resolver/api_set/gem_parser"
|
||||
|
||||
module Bundler
|
||||
class CompactIndexClient
|
||||
class Cache
|
||||
attr_reader :directory
|
||||
|
||||
def initialize(directory, fetcher = nil)
|
||||
@directory = Pathname.new(directory).expand_path
|
||||
@updater = Updater.new(fetcher) if fetcher
|
||||
@mutex = Thread::Mutex.new
|
||||
@endpoints = Set.new
|
||||
|
||||
@info_root = mkdir("info")
|
||||
@special_characters_info_root = mkdir("info-special-characters")
|
||||
@info_etag_root = mkdir("info-etags")
|
||||
end
|
||||
|
||||
def names
|
||||
fetch("names", names_path, names_etag_path)
|
||||
end
|
||||
|
||||
def versions
|
||||
fetch("versions", versions_path, versions_etag_path)
|
||||
end
|
||||
|
||||
def info(name, remote_checksum = nil)
|
||||
path = info_path(name)
|
||||
|
||||
if remote_checksum && remote_checksum != SharedHelpers.checksum_for_file(path, :MD5)
|
||||
fetch("info/#{name}", path, info_etag_path(name))
|
||||
else
|
||||
Bundler::CompactIndexClient.debug { "update skipped info/#{name} (#{remote_checksum ? "versions index checksum is nil" : "versions index checksum matches local"})" }
|
||||
read(path)
|
||||
end
|
||||
end
|
||||
|
||||
def reset!
|
||||
@mutex.synchronize { @endpoints.clear }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def names_path = directory.join("names")
|
||||
def names_etag_path = directory.join("names.etag")
|
||||
def versions_path = directory.join("versions")
|
||||
def versions_etag_path = directory.join("versions.etag")
|
||||
|
||||
def info_path(name)
|
||||
name = name.to_s
|
||||
validate_name!(name)
|
||||
# TODO: converge this into the info_root by hashing all filenames like info_etag_path
|
||||
if /[^a-z0-9_-]/.match?(name)
|
||||
name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}"
|
||||
@special_characters_info_root.join(name)
|
||||
else
|
||||
@info_root.join(name)
|
||||
end
|
||||
end
|
||||
|
||||
def info_etag_path(name)
|
||||
name = name.to_s
|
||||
validate_name!(name)
|
||||
@info_etag_root.join("#{name}-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}")
|
||||
end
|
||||
|
||||
# Gem names come from the remote index and are not otherwise
|
||||
# validated, so refuse anything that would escape the cache
|
||||
# directory when used as a path component.
|
||||
def validate_name!(name)
|
||||
return if File.basename(name) == name
|
||||
|
||||
raise SecurityError, "malformed gem name: #{name.inspect}"
|
||||
end
|
||||
|
||||
def mkdir(name)
|
||||
directory.join(name).tap do |dir|
|
||||
SharedHelpers.filesystem_access(dir) do
|
||||
FileUtils.mkdir_p(dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(remote_path, path, etag_path)
|
||||
if already_fetched?(remote_path)
|
||||
Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
|
||||
else
|
||||
Bundler::CompactIndexClient.debug { "fetching #{remote_path}" }
|
||||
@updater&.update(remote_path, path, etag_path)
|
||||
end
|
||||
|
||||
read(path)
|
||||
end
|
||||
|
||||
def already_fetched?(remote_path)
|
||||
@mutex.synchronize { !@endpoints.add?(remote_path) }
|
||||
end
|
||||
|
||||
def read(path)
|
||||
return unless path.file?
|
||||
SharedHelpers.filesystem_access(path, :read, &:read)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,148 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../vendored_fileutils"
|
||||
require "rubygems/package"
|
||||
|
||||
module Bundler
|
||||
class CompactIndexClient
|
||||
# write cache files in a way that is robust to concurrent modifications
|
||||
# if digests are given, the checksums will be verified
|
||||
class CacheFile
|
||||
DEFAULT_FILE_MODE = 0o644
|
||||
private_constant :DEFAULT_FILE_MODE
|
||||
|
||||
class Error < RuntimeError; end
|
||||
class ClosedError < Error; end
|
||||
|
||||
class DigestMismatchError < Error
|
||||
def initialize(digests, expected_digests)
|
||||
super "Calculated checksums #{digests.inspect} did not match expected #{expected_digests.inspect}."
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize with a copy of the original file, then yield the instance.
|
||||
def self.copy(path, &block)
|
||||
new(path) do |file|
|
||||
file.initialize_digests
|
||||
|
||||
SharedHelpers.filesystem_access(path, :read) do
|
||||
path.open("rb") do |s|
|
||||
file.open {|f| IO.copy_stream(s, f) }
|
||||
end
|
||||
end
|
||||
|
||||
yield file
|
||||
end
|
||||
end
|
||||
|
||||
# Write data to a temp file, then replace the original file with it verifying the digests if given.
|
||||
def self.write(path, data, digests = nil)
|
||||
return unless data
|
||||
new(path) do |file|
|
||||
file.digests = digests
|
||||
file.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :original_path, :path
|
||||
|
||||
def initialize(original_path, &block)
|
||||
@original_path = original_path
|
||||
@perm = original_path.file? ? original_path.stat.mode : DEFAULT_FILE_MODE
|
||||
@path = original_path.sub(/$/, ".#{$$}.tmp")
|
||||
return unless block_given?
|
||||
begin
|
||||
yield self
|
||||
ensure
|
||||
close
|
||||
end
|
||||
end
|
||||
|
||||
def size
|
||||
path.size
|
||||
end
|
||||
|
||||
# initialize the digests using CompactIndexClient::SUPPORTED_DIGESTS, or a subset based on keys.
|
||||
def initialize_digests(keys = nil)
|
||||
@digests = keys ? SUPPORTED_DIGESTS.slice(*keys) : SUPPORTED_DIGESTS.dup
|
||||
@digests.transform_values! {|algo_class| SharedHelpers.digest(algo_class).new }
|
||||
end
|
||||
|
||||
# reset the digests so they don't contain any previously read data
|
||||
def reset_digests
|
||||
@digests&.each_value(&:reset)
|
||||
end
|
||||
|
||||
# set the digests that will be verified at the end
|
||||
def digests=(expected_digests)
|
||||
@expected_digests = expected_digests
|
||||
|
||||
if @expected_digests.nil?
|
||||
@digests = nil
|
||||
elsif @digests
|
||||
@digests = @digests.slice(*@expected_digests.keys)
|
||||
else
|
||||
initialize_digests(@expected_digests.keys)
|
||||
end
|
||||
end
|
||||
|
||||
def digests?
|
||||
@digests&.any?
|
||||
end
|
||||
|
||||
# Open the temp file for writing, reusing original permissions, yielding the IO object.
|
||||
def open(write_mode = "wb", perm = @perm, &block)
|
||||
raise ClosedError, "Cannot reopen closed file" if @closed
|
||||
SharedHelpers.filesystem_access(path, :write) do
|
||||
path.open(write_mode, perm) do |f|
|
||||
yield digests? ? Gem::Package::DigestIO.new(f, @digests) : f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns false without appending when no digests since appending is too error prone to do without digests.
|
||||
def append(data)
|
||||
return false unless digests?
|
||||
open("ab") {|f| f.write data }
|
||||
verify && commit
|
||||
end
|
||||
|
||||
def write(data)
|
||||
reset_digests
|
||||
open {|f| f.write data }
|
||||
commit!
|
||||
end
|
||||
|
||||
def commit!
|
||||
verify || raise(DigestMismatchError.new(@base64digests, @expected_digests))
|
||||
commit
|
||||
end
|
||||
|
||||
# Verify the digests, returning true on match, false on mismatch.
|
||||
def verify
|
||||
return true unless @expected_digests && digests?
|
||||
@base64digests = @digests.transform_values!(&:base64digest)
|
||||
@digests = nil
|
||||
@base64digests.all? {|algo, digest| @expected_digests[algo] == digest }
|
||||
end
|
||||
|
||||
# Replace the original file with the temp file without verifying digests.
|
||||
# The file is permanently closed.
|
||||
def commit
|
||||
raise ClosedError, "Cannot commit closed file" if @closed
|
||||
SharedHelpers.filesystem_access(original_path, :write) do
|
||||
FileUtils.mv(path, original_path)
|
||||
end
|
||||
@closed = true
|
||||
end
|
||||
|
||||
# Remove the temp file without replacing the original file.
|
||||
# The file is permanently closed.
|
||||
def close
|
||||
return if @closed
|
||||
FileUtils.remove_file(path) if @path&.file?
|
||||
@closed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CompactIndexClient
|
||||
class Parser
|
||||
# `compact_index` - an object responding to #names, #versions, #info(name, checksum),
|
||||
# returning the file contents as a string
|
||||
def initialize(compact_index)
|
||||
@compact_index = compact_index
|
||||
@info_checksums = nil
|
||||
@versions_by_name = nil
|
||||
@available = nil
|
||||
@gem_parser = nil
|
||||
end
|
||||
|
||||
def names
|
||||
lines(@compact_index.names)
|
||||
end
|
||||
|
||||
def versions
|
||||
@versions_by_name ||= Hash.new {|hash, key| hash[key] = [] }
|
||||
@info_checksums = {}
|
||||
|
||||
lines(@compact_index.versions).each do |line|
|
||||
name, versions_string, checksum = line.split(" ", 3)
|
||||
@info_checksums[name] = checksum || ""
|
||||
versions_string.split(",") do |version|
|
||||
delete = version.delete_prefix!("-")
|
||||
version = version.split("-", 2).unshift(name)
|
||||
if delete
|
||||
@versions_by_name[name].delete(version)
|
||||
else
|
||||
@versions_by_name[name] << version
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@versions_by_name
|
||||
end
|
||||
|
||||
def info(name)
|
||||
data = @compact_index.info(name, info_checksums[name])
|
||||
lines(data).map {|line| gem_parser.parse(line).unshift(name) }
|
||||
end
|
||||
|
||||
def available?
|
||||
return @available unless @available.nil?
|
||||
@available = !info_checksums.empty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def info_checksums
|
||||
@info_checksums ||= lines(@compact_index.versions).each_with_object({}) do |line, checksums|
|
||||
parse_version_checksum(line, checksums)
|
||||
end
|
||||
end
|
||||
|
||||
def lines(data)
|
||||
return [] if data.nil? || data.empty?
|
||||
lines = data.split("\n")
|
||||
header = lines.index("---")
|
||||
header ? lines[header + 1..-1] : lines
|
||||
end
|
||||
|
||||
def gem_parser
|
||||
@gem_parser ||= Gem::Resolver::APISet::GemParser.new
|
||||
end
|
||||
|
||||
# This is mostly the same as `split(" ", 3)` but it avoids allocating extra objects.
|
||||
# This method gets called at least once for every gem when parsing versions.
|
||||
def parse_version_checksum(line, checksums)
|
||||
return unless (name_end = line.index(" ")) # Artifactory bug causes blank lines in artifactor index files
|
||||
checksum_start = line.index(" ", name_end + 1)
|
||||
return unless checksum_start
|
||||
checksum_start += 1
|
||||
|
||||
checksum_end = line.size - checksum_start
|
||||
|
||||
line.freeze # allows slicing into the string to not allocate a copy of the line
|
||||
name = line[0, name_end]
|
||||
checksum = line[checksum_start, checksum_end]
|
||||
checksums[name.freeze] = checksum # freeze name since it is used as a hash key
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,105 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CompactIndexClient
|
||||
class Updater
|
||||
class MismatchedChecksumError < Error
|
||||
def initialize(path, message)
|
||||
super "The checksum of /#{path} does not match the checksum provided by the server! Something is wrong. #{message}"
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(fetcher)
|
||||
@fetcher = fetcher
|
||||
end
|
||||
|
||||
def update(remote_path, local_path, etag_path)
|
||||
append(remote_path, local_path, etag_path) || replace(remote_path, local_path, etag_path)
|
||||
rescue CacheFile::DigestMismatchError => e
|
||||
raise MismatchedChecksumError.new(remote_path, e.message)
|
||||
rescue Zlib::GzipFile::Error
|
||||
raise Bundler::HTTPError
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def append(remote_path, local_path, etag_path)
|
||||
return false unless local_path.file? && local_path.size.nonzero?
|
||||
|
||||
CacheFile.copy(local_path) do |file|
|
||||
etag = etag_path.read.tap(&:chomp!) if etag_path.file?
|
||||
|
||||
# Subtract a byte to ensure the range won't be empty.
|
||||
# Avoids 416 (Range Not Satisfiable) responses.
|
||||
response = @fetcher.call(remote_path, request_headers(etag, file.size - 1))
|
||||
break true if response.is_a?(Gem::Net::HTTPNotModified)
|
||||
|
||||
file.digests = parse_digests(response)
|
||||
# server may ignore Range and return the full response
|
||||
if response.is_a?(Gem::Net::HTTPPartialContent)
|
||||
tail = response.body.byteslice(1..-1)
|
||||
break false unless tail && file.append(tail)
|
||||
else
|
||||
file.write(response.body)
|
||||
end
|
||||
CacheFile.write(etag_path, etag_from_response(response))
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# request without range header to get the full file or a 304 Not Modified
|
||||
def replace(remote_path, local_path, etag_path)
|
||||
etag = etag_path.read.tap(&:chomp!) if etag_path.file?
|
||||
response = @fetcher.call(remote_path, request_headers(etag))
|
||||
return true if response.is_a?(Gem::Net::HTTPNotModified)
|
||||
CacheFile.write(local_path, response.body, parse_digests(response))
|
||||
CacheFile.write(etag_path, etag_from_response(response))
|
||||
end
|
||||
|
||||
def request_headers(etag, range_start = nil)
|
||||
headers = {}
|
||||
headers["Range"] = "bytes=#{range_start}-" if range_start
|
||||
headers["If-None-Match"] = %("#{etag}") if etag
|
||||
headers
|
||||
end
|
||||
|
||||
def etag_for_request(etag_path)
|
||||
etag_path.read.tap(&:chomp!) if etag_path.file?
|
||||
end
|
||||
|
||||
def etag_from_response(response)
|
||||
return unless response["ETag"]
|
||||
etag = response["ETag"].delete_prefix("W/")
|
||||
return if etag.delete_prefix!('"') && !etag.delete_suffix!('"')
|
||||
etag
|
||||
end
|
||||
|
||||
# Unwraps and returns a Hash of digest algorithms and base64 values
|
||||
# according to RFC 8941 Structured Field Values for HTTP.
|
||||
# https://www.rfc-editor.org/rfc/rfc8941#name-parsing-a-byte-sequence
|
||||
# Ignores unsupported algorithms.
|
||||
def parse_digests(response)
|
||||
return unless header = response["Repr-Digest"] || response["Digest"]
|
||||
digests = {}
|
||||
header.split(",") do |param|
|
||||
algorithm, value = param.split("=", 2)
|
||||
algorithm.strip!
|
||||
algorithm.downcase!
|
||||
next unless SUPPORTED_DIGESTS.key?(algorithm)
|
||||
next unless value = byte_sequence(value)
|
||||
digests[algorithm] = value
|
||||
end
|
||||
digests.empty? ? nil : digests
|
||||
end
|
||||
|
||||
# Unwrap surrounding colons (byte sequence)
|
||||
# The wrapping characters must be matched or we return nil.
|
||||
# Also handles quotes because right now rubygems.org sends them.
|
||||
def byte_sequence(value)
|
||||
return if value.delete_prefix!(":") && !value.delete_suffix!(":")
|
||||
return if value.delete_prefix!('"') && !value.delete_suffix!('"')
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rbconfig"
|
||||
|
||||
module Bundler
|
||||
WINDOWS = RbConfig::CONFIG["host_os"] =~ /(msdos|mswin|djgpp|mingw)/
|
||||
deprecate_constant :WINDOWS
|
||||
|
||||
FREEBSD = RbConfig::CONFIG["host_os"].to_s.include?("bsd")
|
||||
deprecate_constant :FREEBSD
|
||||
|
||||
NULL = File::NULL
|
||||
deprecate_constant :NULL
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "rubygems_ext"
|
||||
|
||||
module Bundler
|
||||
# Returns current version of Ruby
|
||||
#
|
||||
# @return [CurrentRuby] Current version of Ruby
|
||||
def self.current_ruby
|
||||
@current_ruby ||= CurrentRuby.new
|
||||
end
|
||||
|
||||
class CurrentRuby
|
||||
ALL_RUBY_VERSIONS = [*18..27, *30..34, *40..41].freeze
|
||||
KNOWN_MINOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.reverse.join(".") }.freeze
|
||||
KNOWN_MAJOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.last.to_s }.uniq.freeze
|
||||
PLATFORM_MAP = {
|
||||
ruby: [Gem::Platform::RUBY, CurrentRuby::ALL_RUBY_VERSIONS],
|
||||
mri: [Gem::Platform::RUBY, CurrentRuby::ALL_RUBY_VERSIONS],
|
||||
rbx: [Gem::Platform::RUBY],
|
||||
truffleruby: [Gem::Platform::RUBY],
|
||||
jruby: [Gem::Platform::JAVA, [18, 19]],
|
||||
windows: [Gem::Platform::WINDOWS, CurrentRuby::ALL_RUBY_VERSIONS],
|
||||
# deprecated
|
||||
mswin: [Gem::Platform::MSWIN, CurrentRuby::ALL_RUBY_VERSIONS],
|
||||
mswin64: [Gem::Platform::MSWIN64, CurrentRuby::ALL_RUBY_VERSIONS - [18]],
|
||||
mingw: [Gem::Platform::UNIVERSAL_MINGW, CurrentRuby::ALL_RUBY_VERSIONS],
|
||||
x64_mingw: [Gem::Platform::UNIVERSAL_MINGW, CurrentRuby::ALL_RUBY_VERSIONS - [18, 19]],
|
||||
}.each_with_object({}) do |(platform, spec), hash|
|
||||
hash[platform] = spec[0]
|
||||
spec[1]&.each {|version| hash[:"#{platform}_#{version}"] = spec[0] }
|
||||
end.freeze
|
||||
|
||||
def ruby?
|
||||
return true if Bundler::MatchPlatform.generic_local_platform_is_ruby?
|
||||
|
||||
!windows? && (RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev" || RUBY_ENGINE == "truffleruby")
|
||||
end
|
||||
|
||||
def mri?
|
||||
!windows? && RUBY_ENGINE == "ruby"
|
||||
end
|
||||
|
||||
def rbx?
|
||||
ruby? && RUBY_ENGINE == "rbx"
|
||||
end
|
||||
|
||||
def jruby?
|
||||
RUBY_ENGINE == "jruby"
|
||||
end
|
||||
|
||||
def maglev?
|
||||
removed_message =
|
||||
"`CurrentRuby#maglev?` was removed with no replacement. Please use the " \
|
||||
"built-in Ruby `RUBY_ENGINE` constant to check the Ruby implementation you are running on."
|
||||
SharedHelpers.feature_removed!(removed_message)
|
||||
end
|
||||
|
||||
def truffleruby?
|
||||
RUBY_ENGINE == "truffleruby"
|
||||
end
|
||||
|
||||
def windows?
|
||||
Gem.win_platform?
|
||||
end
|
||||
alias_method :mswin?, :windows?
|
||||
alias_method :mswin64?, :windows?
|
||||
alias_method :mingw?, :windows?
|
||||
alias_method :x64_mingw?, :windows?
|
||||
|
||||
(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
|
||||
|
||||
PLATFORM_MAP.keys.each do |platform|
|
||||
define_method(:"#{platform}_#{trimmed_version}?") do
|
||||
send(:"#{platform}?") && send(:"on_#{trimmed_version}?")
|
||||
end
|
||||
end
|
||||
|
||||
define_method(:"maglev_#{trimmed_version}?") do
|
||||
removed_message =
|
||||
"`CurrentRuby##{__method__}` was removed with no replacement. Please use the " \
|
||||
"built-in Ruby `RUBY_ENGINE` and `RUBY_VERSION` constants to perform a similar check."
|
||||
|
||||
SharedHelpers.feature_removed!(removed_message)
|
||||
|
||||
send(:"maglev?") && send(:"on_#{trimmed_version}?")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rubygems/dependency"
|
||||
require_relative "shared_helpers"
|
||||
|
||||
module Bundler
|
||||
class Dependency < Gem::Dependency
|
||||
def initialize(name, version, options = {}, &blk)
|
||||
type = options["type"] || :runtime
|
||||
super(name, version, type)
|
||||
|
||||
@options = options
|
||||
end
|
||||
|
||||
def groups
|
||||
@groups ||= Array(@options["group"] || :default).map(&:to_sym)
|
||||
end
|
||||
|
||||
def source
|
||||
return @source if defined?(@source)
|
||||
|
||||
@source = @options["source"]
|
||||
end
|
||||
|
||||
def path
|
||||
return @path if defined?(@path)
|
||||
|
||||
@path = @options["path"]
|
||||
end
|
||||
|
||||
def git
|
||||
return @git if defined?(@git)
|
||||
|
||||
@git = @options["git"]
|
||||
end
|
||||
|
||||
def github
|
||||
return @github if defined?(@github)
|
||||
|
||||
@github = @options["github"]
|
||||
end
|
||||
|
||||
def branch
|
||||
return @branch if defined?(@branch)
|
||||
|
||||
@branch = @options["branch"]
|
||||
end
|
||||
|
||||
def ref
|
||||
return @ref if defined?(@ref)
|
||||
|
||||
@ref = @options["ref"]
|
||||
end
|
||||
|
||||
def glob
|
||||
return @glob if defined?(@glob)
|
||||
|
||||
@glob = @options["glob"]
|
||||
end
|
||||
|
||||
def platforms
|
||||
@platforms ||= Array(@options["platforms"])
|
||||
end
|
||||
|
||||
def env
|
||||
return @env if defined?(@env)
|
||||
|
||||
@env = @options["env"]
|
||||
end
|
||||
|
||||
def should_include
|
||||
@should_include ||= @options.fetch("should_include", true)
|
||||
end
|
||||
|
||||
def gemfile
|
||||
return @gemfile if defined?(@gemfile)
|
||||
|
||||
@gemfile = @options["gemfile"]
|
||||
end
|
||||
|
||||
def force_ruby_platform
|
||||
return @force_ruby_platform if defined?(@force_ruby_platform)
|
||||
|
||||
@force_ruby_platform = @options["force_ruby_platform"]
|
||||
end
|
||||
|
||||
def autorequire
|
||||
return @autorequire if defined?(@autorequire)
|
||||
|
||||
@autorequire = Array(@options["require"] || []) if @options.key?("require")
|
||||
end
|
||||
|
||||
RUBY_PLATFORM_ARRAY = [Gem::Platform::RUBY].freeze
|
||||
private_constant :RUBY_PLATFORM_ARRAY
|
||||
|
||||
# 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 RUBY_PLATFORM_ARRAY if force_ruby_platform
|
||||
return valid_platforms if platforms.empty?
|
||||
|
||||
valid_platforms.select {|p| expanded_platforms.include?(Gem::Platform.generic(p)) }
|
||||
end
|
||||
|
||||
def expanded_platforms
|
||||
@expanded_platforms ||= platforms.filter_map {|pl| CurrentRuby::PLATFORM_MAP[pl] }.flatten.uniq
|
||||
end
|
||||
|
||||
def should_include?
|
||||
should_include && current_env? && current_platform?
|
||||
end
|
||||
|
||||
def gemspec_dev_dep?
|
||||
@gemspec_dev_dep ||= @options.fetch("gemspec_dev_dep", false)
|
||||
end
|
||||
|
||||
def gemfile_dep?
|
||||
!gemspec_dev_dep?
|
||||
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
|
||||
end
|
||||
|
||||
def specific?
|
||||
super
|
||||
rescue NoMethodError
|
||||
requirement != ">= 0"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "shared_helpers"
|
||||
Bundler::SharedHelpers.feature_removed! "Bundler no longer integrates with " \
|
||||
"Capistrano, but Capistrano provides its own integration with " \
|
||||
"Bundler via the capistrano-bundler gem. Use it instead."
|
||||
@@ -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,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This code was extracted from https://github.com/Solistra/ruby-digest which is under public domain
|
||||
module Bundler
|
||||
module Digest
|
||||
# The initial constant values for the 32-bit constant words A, B, C, D, and
|
||||
# E, respectively.
|
||||
SHA1_WORDS = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0].freeze
|
||||
|
||||
# The 8-bit field used for bitwise `AND` masking. Defaults to `0xFFFFFFFF`.
|
||||
SHA1_MASK = 0xFFFFFFFF
|
||||
|
||||
class << self
|
||||
def sha1(string)
|
||||
unless string.is_a?(String)
|
||||
raise TypeError, "can't convert #{string.class.inspect} into String"
|
||||
end
|
||||
|
||||
buffer = string.b
|
||||
|
||||
words = SHA1_WORDS.dup
|
||||
generate_split_buffer(buffer) do |chunk|
|
||||
w = []
|
||||
chunk.each_slice(4) do |a, b, c, d|
|
||||
w << (((a << 8 | b) << 8 | c) << 8 | d)
|
||||
end
|
||||
a, b, c, d, e = *words
|
||||
(16..79).each do |i|
|
||||
w[i] = SHA1_MASK & rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1)
|
||||
end
|
||||
0.upto(79) do |i|
|
||||
case i
|
||||
when 0..19
|
||||
f = ((b & c) | (~b & d))
|
||||
k = 0x5A827999
|
||||
when 20..39
|
||||
f = (b ^ c ^ d)
|
||||
k = 0x6ED9EBA1
|
||||
when 40..59
|
||||
f = ((b & c) | (b & d) | (c & d))
|
||||
k = 0x8F1BBCDC
|
||||
when 60..79
|
||||
f = (b ^ c ^ d)
|
||||
k = 0xCA62C1D6
|
||||
end
|
||||
t = SHA1_MASK & rotate(a, 5) + f + e + k + w[i]
|
||||
a, b, c, d, e = t, a, SHA1_MASK & rotate(b, 30), c, d # rubocop:disable Style/ParallelAssignment
|
||||
end
|
||||
mutated = [a, b, c, d, e]
|
||||
words.map!.with_index {|word, index| SHA1_MASK & (word + mutated[index]) }
|
||||
end
|
||||
|
||||
words.pack("N*").unpack1("H*")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_split_buffer(string, &block)
|
||||
size = string.bytesize * 8
|
||||
buffer = string.bytes << 128
|
||||
buffer << 0 while buffer.size % 64 != 56
|
||||
buffer.concat([size].pack("Q>").bytes)
|
||||
buffer.each_slice(64, &block)
|
||||
end
|
||||
|
||||
def rotate(value, spaces)
|
||||
value << spaces | value >> (32 - spaces)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,645 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "dependency"
|
||||
require_relative "ruby_dsl"
|
||||
|
||||
module Bundler
|
||||
class Dsl
|
||||
include RubyDsl
|
||||
|
||||
def self.evaluate(gemfile, lockfile, unlock)
|
||||
builder = new
|
||||
builder.lockfile(lockfile)
|
||||
builder.eval_gemfile(gemfile)
|
||||
builder.to_definition(builder.lockfile_path, unlock)
|
||||
end
|
||||
|
||||
VALID_PLATFORMS = Bundler::CurrentRuby::PLATFORM_MAP.keys.freeze
|
||||
|
||||
VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules
|
||||
platform platforms source install_if force_ruby_platform].freeze
|
||||
|
||||
GITHUB_PULL_REQUEST_URL = %r{\Ahttps://github\.com/([A-Za-z0-9_\-\.]+/[A-Za-z0-9_\-\.]+)/pull/(\d+)\z}
|
||||
GITLAB_MERGE_REQUEST_URL = %r{\Ahttps://gitlab\.com/([A-Za-z0-9_\-\./]+)/-/merge_requests/(\d+)\z}
|
||||
|
||||
attr_reader :gemspecs, :gemfile
|
||||
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 = []
|
||||
@lockfile = nil
|
||||
add_git_sources
|
||||
end
|
||||
|
||||
def eval_gemfile(gemfile, contents = nil)
|
||||
with_gemfile(gemfile) do |current_gemfile|
|
||||
contents ||= Bundler.read_file(current_gemfile)
|
||||
instance_eval(contents, current_gemfile, 1)
|
||||
rescue GemfileEvalError => e
|
||||
message = "There was an error evaluating `#{File.basename current_gemfile}`: #{e.message}"
|
||||
raise DSLError.new(message, current_gemfile, e.backtrace, contents)
|
||||
rescue GemfileError, InvalidArgumentError, InvalidOption, DeprecatedError, ScriptError => e
|
||||
message = "There was an error parsing `#{File.basename current_gemfile}`: #{e.message}"
|
||||
raise DSLError.new(message, current_gemfile, e.backtrace, contents)
|
||||
rescue StandardError => e
|
||||
raise unless e.backtrace_locations.first.path == current_gemfile
|
||||
message = "There was an error parsing `#{File.basename current_gemfile}`: #{e.message}"
|
||||
raise DSLError.new(message, current_gemfile, e.backtrace, contents)
|
||||
end
|
||||
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 = SharedHelpers.glob_files_in_dir("{,*}.gemspec", expanded_path).filter_map {|g| Bundler.load_gemspec(g) }
|
||||
gemspecs.reject! {|s| s.name != name } if name
|
||||
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.installable_on_platform?(Bundler.local_platform) } || specs.first
|
||||
|
||||
@gemspecs << spec
|
||||
|
||||
path path, "glob" => glob, "name" => spec.name, "gemspec" => spec do
|
||||
add_dependency spec.name
|
||||
end
|
||||
|
||||
spec.development_dependencies.each do |dep|
|
||||
add_dependency dep.name, dep.requirement.as_list, "gemspec_dev_dep" => true, "group" => development_group
|
||||
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 : {}
|
||||
version = args || [">= 0"]
|
||||
|
||||
normalize_options(name, version, options)
|
||||
|
||||
add_dependency(name, version, options)
|
||||
end
|
||||
|
||||
# For usage in Dsl.evaluate, since lockfile is used as part of the Gemfile.
|
||||
def lockfile_path
|
||||
@lockfile
|
||||
end
|
||||
|
||||
def lockfile(file)
|
||||
@lockfile = file
|
||||
end
|
||||
|
||||
def source(source, *args, &blk)
|
||||
options = args.last.is_a?(Hash) ? args.pop.dup : {}
|
||||
options = normalize_hash(options)
|
||||
source = normalize_source(source)
|
||||
cooldown = options["cooldown"]
|
||||
if cooldown && !(cooldown.is_a?(Integer) && cooldown >= 0)
|
||||
raise InvalidOption, "Expected `cooldown` to be a non-negative integer, got #{cooldown.inspect}"
|
||||
end
|
||||
|
||||
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, "cooldown" => cooldown), &blk)
|
||||
else
|
||||
@sources.add_global_rubygems_remote(source, cooldown: cooldown)
|
||||
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)
|
||||
source_options = normalize_hash(options).merge(
|
||||
"path" => Pathname.new(path),
|
||||
"root_path" => gemfile_root
|
||||
)
|
||||
|
||||
source_options["global"] = true unless block_given?
|
||||
|
||||
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 InvalidArgumentError, "GitHub sources require a block" unless block_given?
|
||||
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)
|
||||
check_primary_source_safety
|
||||
lockfile = @lockfile unless @lockfile.nil?
|
||||
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
|
||||
|
||||
def check_primary_source_safety
|
||||
check_path_source_safety
|
||||
check_rubygems_source_safety
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_dependency(name, version = nil, options = {})
|
||||
options["gemfile"] = @gemfile
|
||||
options["source"] ||= @source
|
||||
options["env"] ||= @env
|
||||
|
||||
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 == name }
|
||||
if current.requirement != dep.requirement
|
||||
current_requirement_open = current.requirements_list.include?(">= 0")
|
||||
|
||||
gemspec_dep = [dep, current].find(&:gemspec_dev_dep?)
|
||||
if gemspec_dep
|
||||
require_relative "vendor/pub_grub/lib/pub_grub/version_range"
|
||||
require_relative "vendor/pub_grub/lib/pub_grub/version_constraint"
|
||||
require_relative "vendor/pub_grub/lib/pub_grub/version_union"
|
||||
require_relative "vendor/pub_grub/lib/pub_grub/rubygems"
|
||||
|
||||
current_gemspec_range = PubGrub::RubyGems.requirement_to_range(current.requirement)
|
||||
next_gemspec_range = PubGrub::RubyGems.requirement_to_range(dep.requirement)
|
||||
|
||||
if current_gemspec_range.intersects?(next_gemspec_range)
|
||||
dep = Dependency.new(name, current.requirement.as_list + dep.requirement.as_list, options)
|
||||
else
|
||||
gemfile_dep = [dep, current].find(&:gemfile_dep?)
|
||||
|
||||
if gemfile_dep
|
||||
raise GemfileError, "The #{name} dependency has conflicting requirements in Gemfile (#{gemfile_dep.requirement}) and gemspec (#{gemspec_dep.requirement})"
|
||||
else
|
||||
raise GemfileError, "Two gemspec development dependencies have conflicting requirements on the same gem: #{dep} and #{current}"
|
||||
end
|
||||
end
|
||||
else
|
||||
update_prompt = ""
|
||||
|
||||
if File.basename(@gemfile) == Injector::INJECTED_GEMS
|
||||
if dep.requirements_list.include?(">= 0") && !current_requirement_open
|
||||
update_prompt = ". Gem already added"
|
||||
else
|
||||
update_prompt = ". If you want to update the gem version, run `bundle update #{name}`"
|
||||
|
||||
update_prompt += ". You may also need to change the version requirement specified in the Gemfile if it's too restrictive." unless current_requirement_open
|
||||
end
|
||||
end
|
||||
|
||||
raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \
|
||||
"You specified: #{name} (#{current.requirement}) and #{name} (#{dep.requirement})" \
|
||||
"#{update_prompt}"
|
||||
end
|
||||
end
|
||||
|
||||
unless current.gemspec_dev_dep? && dep.gemspec_dev_dep?
|
||||
# Always prefer the dependency from the Gemfile
|
||||
if current.gemspec_dev_dep?
|
||||
@dependencies.delete(current)
|
||||
elsif dep.gemspec_dev_dep?
|
||||
return
|
||||
elsif current.source.to_s != dep.source.to_s
|
||||
raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \
|
||||
"You specified that #{name} (#{dep.requirement}) should come from " \
|
||||
"#{current.source || "an unspecified source"} and #{dep.source}\n"
|
||||
else
|
||||
Bundler.ui.warn "Your Gemfile lists the gem #{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.\n" \
|
||||
"While it's not a problem now, it could cause errors if you change the version of one of them later."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@dependencies << dep
|
||||
end
|
||||
|
||||
def with_gemfile(gemfile)
|
||||
expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile&.parent)
|
||||
original_gemfile = @gemfile
|
||||
@gemfile = expanded_gemfile_path
|
||||
@gemfiles << expanded_gemfile_path
|
||||
yield @gemfile.to_s
|
||||
ensure
|
||||
@gemfile = original_gemfile
|
||||
end
|
||||
|
||||
def add_git_sources
|
||||
git_source(:github) do |repo_name|
|
||||
if repo_name =~ GITHUB_PULL_REQUEST_URL
|
||||
{
|
||||
"git" => "https://github.com/#{$1}.git",
|
||||
"branch" => nil,
|
||||
"ref" => "refs/pull/#{$2}/head",
|
||||
"tag" => nil,
|
||||
}
|
||||
else
|
||||
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
||||
"https://github.com/#{repo_name}.git"
|
||||
end
|
||||
end
|
||||
|
||||
git_source(:gist) do |repo_name|
|
||||
"https://gist.github.com/#{repo_name}.git"
|
||||
end
|
||||
|
||||
git_source(:bitbucket) do |repo_name|
|
||||
user_name, repo_name = repo_name.split("/")
|
||||
repo_name ||= user_name
|
||||
"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
|
||||
end
|
||||
|
||||
git_source(:gitlab) do |repo_name|
|
||||
if repo_name =~ GITLAB_MERGE_REQUEST_URL
|
||||
{
|
||||
"git" => "https://gitlab.com/#{$1}.git",
|
||||
"branch" => nil,
|
||||
"ref" => "refs/merge-requests/#{$2}/head",
|
||||
"tag" => nil,
|
||||
}
|
||||
else
|
||||
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
||||
"https://gitlab.com/#{repo_name}.git"
|
||||
end
|
||||
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 /\s/.match?(name)
|
||||
raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace)
|
||||
end
|
||||
raise GemfileError, %(an empty gem name is not valid) if name.empty?
|
||||
|
||||
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
|
||||
|
||||
windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) }
|
||||
if windows_platforms.any?
|
||||
windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ")
|
||||
deprecated_message = "Platform #{windows_platforms} will be removed in the future. Please use platform :windows instead."
|
||||
Bundler::SharedHelpers.feature_deprecated! deprecated_message
|
||||
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]
|
||||
git_opts = @git_sources[git_name].call(opts[git_name])
|
||||
git_opts = { "git" => git_opts } if git_opts.is_a?(String)
|
||||
opts.merge!(git_opts) do |key, _gemfile_value, _git_source_value|
|
||||
raise GemfileError, %(The :#{key} option can't be used with `#{git_name}: #{opts[git_name].inspect}`)
|
||||
end
|
||||
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["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)
|
||||
if opts["branch"] && !(opts["git"] || opts["github"] || (opts.keys & @git_sources.keys.map(&:to_s)).any?)
|
||||
raise GemfileError, %(The `branch` option for `#{command}` is not allowed. Only gems with a git source can specify a branch)
|
||||
end
|
||||
|
||||
invalid_keys = opts.keys - valid_keys
|
||||
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
|
||||
removed_message =
|
||||
"The source :#{source} is disallowed because HTTP requests are insecure.\n" \
|
||||
"Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not."
|
||||
Bundler::SharedHelpers.feature_removed! removed_message
|
||||
when String
|
||||
source
|
||||
else
|
||||
raise GemfileError, "Unknown source '#{source}'"
|
||||
end
|
||||
end
|
||||
|
||||
def check_path_source_safety
|
||||
return if @sources.global_path_source.nil?
|
||||
|
||||
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"
|
||||
|
||||
SharedHelpers.feature_removed! msg.strip
|
||||
end
|
||||
|
||||
def check_rubygems_source_safety
|
||||
multiple_global_source_warning if @sources.aggregate_global_source?
|
||||
end
|
||||
|
||||
def multiple_global_source_warning
|
||||
msg = "This Gemfile contains multiple global sources. " \
|
||||
"Each source after the first must include a block to indicate which gems " \
|
||||
"should come from that source"
|
||||
raise GemfileEvalError, msg
|
||||
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 ||= dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
|
||||
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) } || trace_line
|
||||
return m unless trace_line
|
||||
# Match the line number right before `:in` or the end of the line so a
|
||||
# Windows drive letter like `C:` does not get mistaken for the number.
|
||||
line_number = trace_line[/:(\d+)(?::in\b|\z)/, 1]
|
||||
return m unless line_number
|
||||
line_number = line_number.to_i - 1
|
||||
|
||||
lines = contents.lines.to_a
|
||||
indent = " # "
|
||||
indicator = indent.tr("#", ">")
|
||||
first_line = line_number.zero?
|
||||
last_line = (line_number == (lines.count - 1))
|
||||
|
||||
m << "\n"
|
||||
m << "#{indent}from #{trace_line.gsub(/:in.*$/, "")}\n"
|
||||
m << "#{indent}-------------------------------------------\n"
|
||||
m << "#{indent}#{lines[line_number - 1]}" unless first_line
|
||||
m << "#{indicator}#{lines[line_number]}"
|
||||
m << "#{indent}#{lines[line_number + 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}):\d+)/
|
||||
trace_line = Regexp.last_match[1]
|
||||
description = description.sub(/\n.*\n(\.\.\.)? *\^~+$/, "").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,202 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
# used for Creating Specifications from the Gemcutter Endpoint
|
||||
class EndpointSpecification < Gem::Specification
|
||||
include MatchRemoteMetadata
|
||||
|
||||
attr_reader :name, :version, :platform, :checksum, :created_at
|
||||
attr_writer :dependencies
|
||||
attr_accessor :remote, :locked_platform
|
||||
|
||||
def initialize(name, version, platform, spec_fetcher, dependencies, metadata = nil)
|
||||
super()
|
||||
@name = name
|
||||
@version = Gem::Version.create version
|
||||
@platform = Gem::Platform.new(platform)
|
||||
@spec_fetcher = spec_fetcher
|
||||
@dependencies = nil
|
||||
@unbuilt_dependencies = dependencies
|
||||
|
||||
@loaded_from = nil
|
||||
@remote_specification = nil
|
||||
@locked_platform = nil
|
||||
|
||||
parse_metadata(metadata)
|
||||
end
|
||||
|
||||
def insecurely_materialized?
|
||||
@locked_platform.to_s != @platform.to_s
|
||||
end
|
||||
|
||||
def fetch_platform
|
||||
@platform
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@dependencies ||= @unbuilt_dependencies.map! {|dep, reqs| build_dependency(dep, reqs) }
|
||||
end
|
||||
alias_method :runtime_dependencies, :dependencies
|
||||
|
||||
# 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
|
||||
|
||||
# `require_paths` is overridden above, but `full_require_paths` (and so
|
||||
# `load_paths`) is computed from `raw_require_paths`, which would otherwise
|
||||
# report the default `lib` for every gem
|
||||
def raw_require_paths
|
||||
if @remote_specification
|
||||
@remote_specification.raw_require_paths
|
||||
elsif _local_specification
|
||||
_local_specification.raw_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
|
||||
|
||||
# needed for `bundle fund`
|
||||
def metadata
|
||||
if @remote_specification
|
||||
@remote_specification.metadata
|
||||
elsif _local_specification
|
||||
_local_specification.metadata
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def _local_specification
|
||||
return unless @loaded_from && File.exist?(local_specification_path)
|
||||
eval(File.read(local_specification_path), nil, 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
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} @name=\"#{name}\" (#{full_name.delete_prefix("#{name}-")})>"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _remote_specification
|
||||
@_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, @platform])
|
||||
end
|
||||
|
||||
def local_specification_path
|
||||
"#{base_dir}/specifications/#{full_name}.gemspec"
|
||||
end
|
||||
|
||||
def parse_metadata(data)
|
||||
unless data
|
||||
@required_ruby_version = nil
|
||||
@required_rubygems_version = nil
|
||||
@created_at = nil
|
||||
return
|
||||
end
|
||||
|
||||
data.each do |k, v|
|
||||
next unless v
|
||||
case k.to_s
|
||||
when "checksum"
|
||||
# Some registries send empty checksum values, treat them as if the
|
||||
# checksum was not included at all
|
||||
checksum = v.last
|
||||
next unless checksum
|
||||
|
||||
begin
|
||||
@checksum = Checksum.from_api(checksum, @spec_fetcher.uri)
|
||||
rescue ArgumentError => e
|
||||
raise ArgumentError, "Invalid checksum for #{full_name}: #{e.message}"
|
||||
end
|
||||
when "rubygems"
|
||||
@required_rubygems_version = Gem::Requirement.new(v)
|
||||
when "ruby"
|
||||
@required_ruby_version = Gem::Requirement.new(v)
|
||||
when "created_at"
|
||||
value = v.is_a?(Array) ? v.last : v
|
||||
if value.is_a?(String)
|
||||
@created_at = begin
|
||||
Time.new(value)
|
||||
rescue ArgumentError
|
||||
nil
|
||||
end
|
||||
end
|
||||
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)
|
||||
Dependency.new(name, requirements)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,148 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "rubygems_integration"
|
||||
require_relative "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### #{SharedHelpers.relative_path_to(gemfile)}\n\n"
|
||||
out << "```ruby\n" << read_file(gemfile).chomp << "\n```\n"
|
||||
end
|
||||
|
||||
out << "\n### #{SharedHelpers.relative_path_to(Bundler.default_lockfile)}\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
|
||||
"#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{Gem::Platform.local}]"
|
||||
end
|
||||
|
||||
def self.git_version
|
||||
Bundler::Source::Git::GitProxy.new(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", Gem.dir]
|
||||
out << [" Gem Path", Gem.path.join(File::PATH_SEPARATOR)]
|
||||
out << [" User Home", Gem.user_home]
|
||||
out << [" User Path", Gem.user_dir]
|
||||
out << [" Bin Dir", Gem.bindir]
|
||||
if defined?(OpenSSL::SSL)
|
||||
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_locations.last.absolute_path)&.match? %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,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class EnvironmentPreserver
|
||||
INTENTIONALLY_NIL = "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL"
|
||||
BUNDLER_KEYS = %w[
|
||||
BUNDLE_BIN_PATH
|
||||
BUNDLE_GEMFILE
|
||||
BUNDLE_LOCKFILE
|
||||
BUNDLER_VERSION
|
||||
BUNDLER_SETUP
|
||||
GEM_HOME
|
||||
GEM_PATH
|
||||
MANPATH
|
||||
PATH
|
||||
RB_USER_INSTALL
|
||||
RUBYLIB
|
||||
RUBYOPT
|
||||
].map(&:freeze).freeze
|
||||
BUNDLER_PREFIX = "BUNDLER_ORIG_"
|
||||
|
||||
def self.from_env
|
||||
new(ENV.to_hash, BUNDLER_KEYS)
|
||||
end
|
||||
|
||||
# @param env [Hash]
|
||||
# @param keys [Array<String>]
|
||||
def initialize(env, keys)
|
||||
@original = env
|
||||
@keys = keys
|
||||
@prefix = BUNDLER_PREFIX
|
||||
end
|
||||
|
||||
# Replaces `ENV` with the bundler environment variables backed up
|
||||
def replace_with_backup
|
||||
ENV.replace(backup)
|
||||
end
|
||||
|
||||
# @return [Hash]
|
||||
def backup
|
||||
env = @original.clone
|
||||
@keys.each do |key|
|
||||
value = env[key]
|
||||
if !value.nil?
|
||||
env[@prefix + key] ||= value
|
||||
else
|
||||
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?
|
||||
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,277 @@
|
||||
# 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 SolveFailure < BundlerError; status_code(6); end
|
||||
|
||||
class GemNotFound < BundlerError; status_code(7); end
|
||||
class InstallHookError < BundlerError; status_code(8); end
|
||||
class RemovedError < BundlerError; status_code(9); 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 ThreadCreationError < BundlerError; status_code(33); end
|
||||
class APIResponseMismatchError < BundlerError; status_code(34); end
|
||||
class APIResponseInvalidDependenciesError < BundlerError; status_code(35); end
|
||||
class GemfileEvalError < GemfileError; end
|
||||
class MarshalError < StandardError; end
|
||||
|
||||
class ChecksumMismatchError < SecurityError
|
||||
def initialize(lock_name, existing, checksum)
|
||||
@lock_name = lock_name
|
||||
@existing = existing
|
||||
@checksum = checksum
|
||||
end
|
||||
|
||||
def message
|
||||
<<~MESSAGE
|
||||
Bundler found mismatched checksums. This is a potential security risk.
|
||||
#{@lock_name} #{@existing.to_lock}
|
||||
from #{@existing.sources.join("\n and ")}
|
||||
#{@lock_name} #{@checksum.to_lock}
|
||||
from #{@checksum.sources.join("\n and ")}
|
||||
|
||||
#{mismatch_resolution_instructions}
|
||||
To ignore checksum security warnings, disable checksum validation with
|
||||
`bundle config set --local disable_checksum_validation true`
|
||||
MESSAGE
|
||||
end
|
||||
|
||||
def mismatch_resolution_instructions
|
||||
removable, remote = [@existing, @checksum].partition(&:removable?)
|
||||
case removable.size
|
||||
when 1
|
||||
msg = +"If you trust #{remote.first.sources.first}, to resolve this issue you can:\n"
|
||||
msg << removable.first.removal_instructions
|
||||
when 2
|
||||
msg = +"To resolve this issue you can either:\n"
|
||||
msg << @checksum.removal_instructions
|
||||
msg << "or if you are sure that the new checksum from #{@checksum.sources.first} is correct:\n"
|
||||
msg << @existing.removal_instructions
|
||||
end
|
||||
end
|
||||
|
||||
status_code(37)
|
||||
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 permission_type
|
||||
case @permission_type
|
||||
when :create
|
||||
"executable permissions for all parent directories and write permissions for `#{parent_folder}`"
|
||||
else
|
||||
"#{@permission_type} permissions for that path"
|
||||
end
|
||||
end
|
||||
|
||||
def parent_folder
|
||||
File.dirname(@path)
|
||||
end
|
||||
|
||||
def message
|
||||
"There was an error while trying to #{action} `#{@path}`. " \
|
||||
"It is likely that you need to grant #{permission_type}."
|
||||
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 ReadOnlyFileSystemError < PermissionError
|
||||
def message
|
||||
"There was an error while trying to #{action} `#{@path}`. " \
|
||||
"File system is read-only."
|
||||
end
|
||||
|
||||
status_code(42)
|
||||
end
|
||||
|
||||
class OperationNotPermittedError < PermissionError
|
||||
def message
|
||||
"There was an error while trying to #{action} `#{@path}`. " \
|
||||
"Underlying OS system call raised an EPERM error."
|
||||
end
|
||||
|
||||
status_code(43)
|
||||
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
|
||||
|
||||
class DirectoryRemovalError < BundlerError
|
||||
def initialize(orig_exception, msg)
|
||||
full_message = "#{msg}.\n" \
|
||||
"The underlying error was #{orig_exception.class}: #{orig_exception.message}, with backtrace:\n" \
|
||||
" #{orig_exception.backtrace.join("\n ")}\n\n" \
|
||||
"Bundler Error Backtrace:"
|
||||
super(full_message)
|
||||
end
|
||||
|
||||
status_code(36)
|
||||
end
|
||||
|
||||
class InsecureInstallPathError < BundlerError
|
||||
def initialize(name, path)
|
||||
@name = name
|
||||
@path = path
|
||||
end
|
||||
|
||||
def message
|
||||
"Bundler cannot reinstall #{@name} because there's a previous installation of it at #{@path} that is unsafe to remove.\n" \
|
||||
"The parent of #{@path} is world-writable and does not have the sticky bit set, making it insecure to remove due to potential vulnerabilities.\n" \
|
||||
"Please change the permissions of #{File.dirname(@path)} or choose a different install path."
|
||||
end
|
||||
|
||||
status_code(38)
|
||||
end
|
||||
|
||||
class CorruptBundlerInstallError < BundlerError
|
||||
def initialize(loaded_spec)
|
||||
@loaded_spec = loaded_spec
|
||||
end
|
||||
|
||||
def message
|
||||
"The running version of Bundler (#{Bundler::VERSION}) does not match the version of the specification installed for it (#{@loaded_spec.version}). " \
|
||||
"This can be caused by reinstalling Ruby without removing previous installation, leaving around an upgraded default version of Bundler. " \
|
||||
"Reinstalling Ruby from scratch should fix the problem."
|
||||
end
|
||||
|
||||
status_code(39)
|
||||
end
|
||||
|
||||
class InvalidArgumentError < BundlerError; status_code(40); end
|
||||
|
||||
class IncorrectLockfileDependencies < BundlerError
|
||||
attr_reader :spec
|
||||
|
||||
def initialize(spec)
|
||||
@spec = spec
|
||||
end
|
||||
|
||||
def message
|
||||
"Bundler found incorrect dependencies in the lockfile for #{spec.full_name}"
|
||||
end
|
||||
|
||||
status_code(41)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class FeatureFlag
|
||||
(1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } }
|
||||
|
||||
def removed_major?(target_major_version)
|
||||
@major_version > target_major_version
|
||||
end
|
||||
|
||||
def deprecated_major?(target_major_version)
|
||||
@major_version >= target_major_version
|
||||
end
|
||||
|
||||
def initialize(bundler_version)
|
||||
@bundler_version = Gem::Version.create(bundler_version)
|
||||
@major_version = @bundler_version.segments.first
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,371 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "vendored_persistent"
|
||||
require_relative "vendored_timeout"
|
||||
require_relative "vendored_securerandom"
|
||||
require "zlib"
|
||||
|
||||
module Bundler
|
||||
# Handles all the fetching with the rubygems server
|
||||
class Fetcher
|
||||
autoload :Base, File.expand_path("fetcher/base", __dir__)
|
||||
autoload :CompactIndex, File.expand_path("fetcher/compact_index", __dir__)
|
||||
autoload :Downloader, File.expand_path("fetcher/downloader", __dir__)
|
||||
autoload :Dependency, File.expand_path("fetcher/dependency", __dir__)
|
||||
autoload :Index, File.expand_path("fetcher/index", __dir__)
|
||||
|
||||
# This error is raised when it looks like the network is down
|
||||
class NetworkDownError < HTTPError; end
|
||||
# This error is raised if we should rate limit our requests to the API
|
||||
class TooManyRequestsError < 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" \
|
||||
" https://railsapps.github.io/openssl-certificate-verify-failed.html."
|
||||
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 "Could not load OpenSSL.\n" \
|
||||
"You must recompile Ruby with OpenSSL support.\n" \
|
||||
"original error: #{msg}\n"
|
||||
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 set --global #{remote_uri} username:password`\n" \
|
||||
"or by storing the credentials in the `#{Settings.key_for(remote_uri)}` environment variable"
|
||||
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
|
||||
|
||||
# This error is raised if HTTP authentication is correct, but lacks
|
||||
# necessary permissions.
|
||||
class AuthenticationForbiddenError < HTTPError
|
||||
def initialize(remote_uri)
|
||||
remote_uri = filter_uri(remote_uri)
|
||||
super "Access token could not be authenticated for #{remote_uri}.\n" \
|
||||
"Make sure it's valid and has the necessary scopes configured."
|
||||
end
|
||||
end
|
||||
|
||||
HTTP_ERRORS = (Downloader::HTTP_RETRYABLE_ERRORS + Downloader::HTTP_NON_RETRYABLE_ERRORS).freeze
|
||||
deprecate_constant :HTTP_ERRORS
|
||||
|
||||
NET_ERRORS = [
|
||||
:HTTPBadGateway,
|
||||
:HTTPBadRequest,
|
||||
:HTTPFailedDependency,
|
||||
:HTTPForbidden,
|
||||
:HTTPInsufficientStorage,
|
||||
:HTTPMethodNotAllowed,
|
||||
:HTTPMovedPermanently,
|
||||
:HTTPNoContent,
|
||||
:HTTPNotFound,
|
||||
:HTTPNotImplemented,
|
||||
:HTTPPreconditionFailed,
|
||||
:HTTPRequestEntityTooLarge,
|
||||
:HTTPRequestURITooLong,
|
||||
:HTTPUnauthorized,
|
||||
:HTTPUnprocessableEntity,
|
||||
:HTTPUnsupportedMediaType,
|
||||
:HTTPVersionNotSupported,
|
||||
].freeze
|
||||
deprecate_constant :NET_ERRORS
|
||||
|
||||
# Exceptions classes that should bypass retry attempts. If your password didn't work the
|
||||
# first time, it's not going to the third time.
|
||||
FAIL_ERRORS = [
|
||||
AuthenticationRequiredError,
|
||||
BadAuthenticationError,
|
||||
AuthenticationForbiddenError,
|
||||
FallbackError,
|
||||
SecurityError,
|
||||
Gem::Requirement::BadRequirementError,
|
||||
Gem::Net::HTTPBadGateway,
|
||||
Gem::Net::HTTPBadRequest,
|
||||
Gem::Net::HTTPFailedDependency,
|
||||
Gem::Net::HTTPForbidden,
|
||||
Gem::Net::HTTPInsufficientStorage,
|
||||
Gem::Net::HTTPMethodNotAllowed,
|
||||
Gem::Net::HTTPMovedPermanently,
|
||||
Gem::Net::HTTPNoContent,
|
||||
Gem::Net::HTTPNotFound,
|
||||
Gem::Net::HTTPNotImplemented,
|
||||
Gem::Net::HTTPPreconditionFailed,
|
||||
Gem::Net::HTTPRequestEntityTooLarge,
|
||||
Gem::Net::HTTPRequestURITooLong,
|
||||
Gem::Net::HTTPUnauthorized,
|
||||
Gem::Net::HTTPUnprocessableEntity,
|
||||
Gem::Net::HTTPUnsupportedMediaType,
|
||||
Gem::Net::HTTPVersionNotSupported,
|
||||
].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)
|
||||
@cis = nil
|
||||
@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 = Gem::URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
|
||||
spec = if uri.scheme == "file"
|
||||
path = Gem::Util.correct_for_windows_path(uri.path)
|
||||
Bundler.safe_load_marshal Bundler.rubygems.inflate(Gem.read_binary(path))
|
||||
elsif cached_spec_path = gemspec_cached_path(spec_file_name)
|
||||
Bundler.load_gemspec(cached_spec_path)
|
||||
else
|
||||
Bundler.safe_load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body)
|
||||
end
|
||||
raise MarshalError, "is #{spec.inspect}" unless spec.is_a?(Gem::Specification)
|
||||
spec
|
||||
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)
|
||||
index = Bundler::Index.new
|
||||
|
||||
fetch_specs(gem_names).each do |name, version, platform, dependencies, metadata|
|
||||
spec = if dependencies
|
||||
EndpointSpecification.new(name, version, platform, self, dependencies, metadata).tap do |es|
|
||||
source.checksum_store.replace(es, es.checksum)
|
||||
end
|
||||
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 && api_fetcher? # newline after dots
|
||||
raise
|
||||
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
|
||||
|
||||
# Some settings keys embed a URI (e.g. `mirror.<uri>`) whose userinfo can
|
||||
# carry credentials. Strip the userinfo before advertising the setting
|
||||
# names so we don't leak secrets to every gem source we talk to.
|
||||
options = Bundler.settings.all.map do |key|
|
||||
key.include?("://") ? key.gsub(%r{//[^/@]+@}, "//") : key
|
||||
end
|
||||
agent << " options/#{options.join(",")}"
|
||||
|
||||
agent << " ci/#{cis.join(",")}" if cis.any?
|
||||
|
||||
# add a random ID so we can consolidate runs server-side
|
||||
agent << " " << Gem::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 http_proxy
|
||||
return unless uri = connection.proxy_uri
|
||||
uri.to_s
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:0x#{object_id} uri=#{uri}>"
|
||||
end
|
||||
|
||||
def api_fetcher?
|
||||
fetchers.first.api_fetcher?
|
||||
end
|
||||
|
||||
def release_resolution_memory!
|
||||
@fetchers&.each(&:release_resolution_memory!)
|
||||
end
|
||||
|
||||
def gem_remote_fetcher
|
||||
@gem_remote_fetcher ||= begin
|
||||
require_relative "fetcher/gem_remote_fetcher"
|
||||
fetcher = GemRemoteFetcher.new Gem.configuration[:http_proxy]
|
||||
fetcher.headers["User-Agent"] = user_agent
|
||||
fetcher.headers["X-Gemfile-Source"] = @remote.original_uri.to_s if @remote.original_uri
|
||||
fetcher
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_fetchers
|
||||
if Bundler::Fetcher.disable_endpoint
|
||||
[Index]
|
||||
elsif remote_uri.scheme == "file"
|
||||
Bundler.ui.debug("Using a local server, bundler won't use the CompactIndex API")
|
||||
[Index]
|
||||
else
|
||||
[CompactIndex, Dependency, Index]
|
||||
end
|
||||
end
|
||||
|
||||
def fetchers
|
||||
@fetchers ||= available_fetchers.map {|f| f.new(downloader, @remote, uri, gem_remote_fetcher) }.drop_while {|f| !f.available? }
|
||||
end
|
||||
|
||||
def fetch_specs(gem_names)
|
||||
fetchers.reject!(&:api_fetcher?) unless gem_names
|
||||
fetchers.reject! do |f|
|
||||
specs = f.specs(gem_names)
|
||||
return specs if specs
|
||||
true
|
||||
end
|
||||
[]
|
||||
end
|
||||
|
||||
def cis
|
||||
@cis ||= Bundler::CIDetector.ci_strings
|
||||
end
|
||||
|
||||
def connection
|
||||
@connection ||= begin
|
||||
needs_ssl = remote_uri.scheme == "https" ||
|
||||
Bundler.settings[:ssl_verify_mode] ||
|
||||
Bundler.settings[:ssl_client_cert]
|
||||
if needs_ssl
|
||||
begin
|
||||
require "openssl"
|
||||
rescue StandardError, LoadError => e
|
||||
raise SSLError.new(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
con = Gem::Net::HTTP::Persistent.new name: "bundler", proxy: :ENV
|
||||
if gem_proxy = Gem.configuration[:http_proxy]
|
||||
con.proxy = Gem::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] ||
|
||||
(Gem.configuration.ssl_client_cert if
|
||||
Gem.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.read(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.find {|path| File.file? path }
|
||||
end
|
||||
|
||||
def bundler_cert_store
|
||||
store = OpenSSL::X509::Store.new
|
||||
ssl_ca_cert = Bundler.settings[:ssl_ca_cert] ||
|
||||
(Gem.configuration.ssl_ca_cert if
|
||||
Gem.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
|
||||
require "rubygems/request"
|
||||
Gem::Request.get_cert_files.each {|c| store.add_file c }
|
||||
end
|
||||
store
|
||||
end
|
||||
|
||||
def remote_uri
|
||||
@remote.uri
|
||||
end
|
||||
|
||||
def downloader
|
||||
@downloader ||= Downloader.new(connection, self.class.redirect_limit)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Fetcher
|
||||
class Base
|
||||
attr_reader :downloader
|
||||
attr_reader :display_uri
|
||||
attr_reader :remote
|
||||
attr_reader :gem_remote_fetcher
|
||||
|
||||
def initialize(downloader, remote, display_uri, gem_remote_fetcher)
|
||||
raise "Abstract class" if self.class == Base
|
||||
@downloader = downloader
|
||||
@remote = remote
|
||||
@display_uri = display_uri
|
||||
@gem_remote_fetcher = gem_remote_fetcher
|
||||
end
|
||||
|
||||
def remote_uri
|
||||
@remote.uri
|
||||
end
|
||||
|
||||
def fetch_uri
|
||||
@fetch_uri ||= if remote_uri.host == "rubygems.org"
|
||||
uri = remote_uri.dup
|
||||
uri.host = "index.rubygems.org"
|
||||
uri
|
||||
else
|
||||
remote_uri
|
||||
end
|
||||
end
|
||||
|
||||
def available?
|
||||
true
|
||||
end
|
||||
|
||||
def api_fetcher?
|
||||
false
|
||||
end
|
||||
|
||||
def release_resolution_memory!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_specs(&block)
|
||||
if Bundler.ui.debug?
|
||||
Bundler.ui.debug yield
|
||||
else
|
||||
Bundler.ui.info ".", false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,133 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "base"
|
||||
require_relative "../worker"
|
||||
|
||||
module Bundler
|
||||
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|
|
||||
method.bind_call(self, *args, &blk)
|
||||
rescue NetworkDownError, CompactIndexClient::Updater::MismatchedChecksumError => e
|
||||
raise HTTPError, e.message
|
||||
rescue AuthenticationRequiredError, BadAuthenticationError
|
||||
# Fail since we got a 401 from the server.
|
||||
raise
|
||||
rescue HTTPError => e
|
||||
Bundler.ui.trace(e)
|
||||
nil
|
||||
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 = fetch_gem_infos(remaining_gems).flatten(1)
|
||||
next_gems = deps.flat_map {|d| d[CompactIndexClient::INFO_DEPS].flat_map(&:first) }.uniq
|
||||
deps.each {|dep| gem_info << dep }
|
||||
complete_gems.concat(deps.map(&:first)).uniq!
|
||||
remaining_gems = next_gems - complete_gems
|
||||
end
|
||||
@bundle_worker&.stop
|
||||
@bundle_worker = nil # reset it. Not sure if necessary
|
||||
|
||||
gem_info
|
||||
end
|
||||
|
||||
def available?
|
||||
unless SharedHelpers.md5_available?
|
||||
Bundler.ui.debug("FIPS mode is enabled, bundler can't use the CompactIndex API")
|
||||
return nil
|
||||
end
|
||||
# Read info file checksums out of /versions, so we can know if gems are up to date
|
||||
compact_index_client.available?
|
||||
rescue CompactIndexClient::Updater::MismatchedChecksumError => e
|
||||
Bundler.ui.debug(e.message)
|
||||
nil
|
||||
end
|
||||
compact_index_request :available?
|
||||
|
||||
def api_fetcher?
|
||||
true
|
||||
end
|
||||
|
||||
# The client holds the parsed checksums of all info files in the
|
||||
# index. Dropping it is always safe because it is rebuilt from the
|
||||
# local cache on demand.
|
||||
def release_resolution_memory!
|
||||
@compact_index_client = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compact_index_client
|
||||
@compact_index_client ||=
|
||||
SharedHelpers.filesystem_access(cache_path) do
|
||||
CompactIndexClient.new(cache_path, client_fetcher)
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_gem_infos(names)
|
||||
# Create the client and update the versions file on this thread.
|
||||
# Otherwise the workers race to lazily create the client and update
|
||||
# the versions file concurrently, e.g. when the client was released
|
||||
# after resolution and is being rebuilt for `bundle cache`.
|
||||
compact_index_client.available?
|
||||
|
||||
in_parallel(names) {|name| compact_index_client.info(name) }
|
||||
rescue TooManyRequestsError # rubygems.org is rate limiting us, slow down.
|
||||
@bundle_worker&.stop
|
||||
@bundle_worker = nil # reset it. Not sure if necessary
|
||||
compact_index_client.reset!
|
||||
names.map {|name| compact_index_client.info(name) }
|
||||
end
|
||||
|
||||
def in_parallel(inputs, &blk)
|
||||
func = lambda {|object, _index| blk.call(object) }
|
||||
worker = bundle_worker(func)
|
||||
inputs.each {|input| worker.enq(input) }
|
||||
inputs.map { worker.deq }
|
||||
end
|
||||
|
||||
def bundle_worker(func = nil)
|
||||
@bundle_worker ||= begin
|
||||
worker_name = "Compact Index (#{display_uri.host})"
|
||||
Bundler::Worker.new(Bundler.settings.processor_count, 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 headers["If-None-Match"]
|
||||
ui.warn "Using the cached data for the new index because of a network error: #{e}"
|
||||
Gem::Net::HTTPNotModified.new(nil, nil, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "base"
|
||||
require "cgi/escape"
|
||||
require "cgi/util" unless defined?(CGI::EscapeExt)
|
||||
|
||||
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, 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, trying the 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(api_request_size) do |names|
|
||||
marshalled_deps = downloader.fetch(dependency_api_uri(names)).body
|
||||
gem_list.concat(Bundler.safe_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
|
||||
|
||||
private
|
||||
|
||||
def api_request_size
|
||||
Bundler.settings[:api_request_size]&.to_i || Source::Rubygems::API_REQUEST_SIZE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,125 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Fetcher
|
||||
class Downloader
|
||||
HTTP_NON_RETRYABLE_ERRORS = [
|
||||
SocketError,
|
||||
Errno::EADDRNOTAVAIL,
|
||||
Errno::ENETDOWN,
|
||||
Errno::ENETUNREACH,
|
||||
Gem::Net::HTTP::Persistent::Error,
|
||||
Errno::EHOSTUNREACH,
|
||||
].freeze
|
||||
|
||||
HTTP_RETRYABLE_ERRORS = [
|
||||
Gem::Timeout::Error,
|
||||
EOFError,
|
||||
Errno::EINVAL,
|
||||
Errno::ECONNRESET,
|
||||
Errno::ETIMEDOUT,
|
||||
Errno::EAGAIN,
|
||||
Gem::Net::HTTPBadResponse,
|
||||
Gem::Net::HTTPHeaderSyntaxError,
|
||||
Gem::Net::ProtocolError,
|
||||
Zlib::BufError,
|
||||
].freeze
|
||||
|
||||
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
|
||||
|
||||
filtered_uri = URICredentialsFilter.credential_filtered_uri(uri)
|
||||
|
||||
response = request(uri, headers)
|
||||
Bundler.ui.debug("HTTP #{response.code} #{response.message} #{filtered_uri}")
|
||||
|
||||
case response
|
||||
when Gem::Net::HTTPSuccess, Gem::Net::HTTPNotModified
|
||||
response
|
||||
when Gem::Net::HTTPRedirection
|
||||
new_uri = Gem::URI.parse(response["location"])
|
||||
# Following a downgrade would put the credentials this request carries
|
||||
# on a plaintext connection, so refuse it like Gem::RemoteFetcher does.
|
||||
if https?(uri) && !https?(new_uri)
|
||||
raise HTTPError, "Redirecting to a non-https URI is not allowed: #{URICredentialsFilter.credential_filtered_uri(new_uri)}"
|
||||
end
|
||||
if new_uri.host == uri.host
|
||||
new_uri.user = uri.user
|
||||
new_uri.password = uri.password
|
||||
end
|
||||
fetch(new_uri, headers, counter + 1)
|
||||
when Gem::Net::HTTPRequestedRangeNotSatisfiable
|
||||
new_headers = headers.dup
|
||||
new_headers.delete("Range")
|
||||
fetch(uri, new_headers)
|
||||
when Gem::Net::HTTPRequestEntityTooLarge
|
||||
raise FallbackError, response.body
|
||||
when Gem::Net::HTTPTooManyRequests
|
||||
raise TooManyRequestsError, response.body
|
||||
when Gem::Net::HTTPUnauthorized
|
||||
raise BadAuthenticationError, uri.host if uri.userinfo
|
||||
raise AuthenticationRequiredError, uri.host
|
||||
when Gem::Net::HTTPForbidden
|
||||
raise AuthenticationForbiddenError, uri.host
|
||||
when Gem::Net::HTTPNotFound
|
||||
raise FallbackError, "Gem::Net::HTTPNotFound: #{filtered_uri}"
|
||||
else
|
||||
message = "Gem::#{response.class.name.gsub(/\AGem::/, "")}"
|
||||
message += ": #{response.body}" unless response.body.empty?
|
||||
raise HTTPError, message
|
||||
end
|
||||
end
|
||||
|
||||
def request(uri, headers)
|
||||
validate_uri_scheme!(uri)
|
||||
|
||||
filtered_uri = URICredentialsFilter.credential_filtered_uri(uri)
|
||||
|
||||
Bundler.ui.debug "HTTP GET #{filtered_uri}"
|
||||
req = Gem::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 OpenSSL::SSL::SSLError
|
||||
raise CertificateFailureError.new(uri)
|
||||
rescue *HTTP_NON_RETRYABLE_ERRORS => e
|
||||
Bundler.ui.trace e
|
||||
|
||||
host = uri.host
|
||||
host_port = "#{host}:#{uri.port}"
|
||||
host = host_port if filtered_uri.to_s.include?(host_port)
|
||||
raise NetworkDownError, "Could not reach host #{host}. Check your network " \
|
||||
"connection and try again."
|
||||
rescue *HTTP_RETRYABLE_ERRORS => e
|
||||
Bundler.ui.trace e
|
||||
|
||||
raise HTTPError, "Network error while fetching #{filtered_uri}" \
|
||||
" (#{e})"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def https?(uri)
|
||||
uri.scheme&.casecmp("https")&.zero?
|
||||
end
|
||||
|
||||
def validate_uri_scheme!(uri)
|
||||
return if /\Ahttps?\z/.match?(uri.scheme)
|
||||
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,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rubygems/remote_fetcher"
|
||||
|
||||
module Bundler
|
||||
class Fetcher
|
||||
class GemRemoteFetcher < Gem::RemoteFetcher
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
@pool_size = Bundler.settings.installation_parallelization
|
||||
ssl_ca_cert = Bundler.settings[:ssl_ca_cert]
|
||||
@cert_files << ssl_ca_cert if ssl_ca_cert
|
||||
end
|
||||
|
||||
def request(*args)
|
||||
super do |req|
|
||||
req.delete("User-Agent") if headers["User-Agent"]
|
||||
yield req if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "base"
|
||||
|
||||
module Bundler
|
||||
class Fetcher
|
||||
class Index < Base
|
||||
def specs(_gem_names)
|
||||
Bundler.rubygems.fetch_all_remote_specs(remote, gem_remote_fetcher)
|
||||
rescue Gem::RemoteFetcher::FetchError => e
|
||||
case e.message
|
||||
when /certificate verify failed/
|
||||
raise CertificateFailureError.new(display_uri)
|
||||
when /401/
|
||||
raise BadAuthenticationError, remote_uri if remote_uri.userinfo
|
||||
raise AuthenticationRequiredError, remote_uri
|
||||
when /403/
|
||||
raise AuthenticationForbiddenError, remote_uri
|
||||
else
|
||||
raise HTTPError, "Could not fetch specs from #{display_uri} due to underlying error <#{e.message}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
module ForcePlatform
|
||||
# The `:force_ruby_platform` value used by dependencies for resolution, and
|
||||
# by locked specifications for materialization is `false` by default, except
|
||||
# for TruffleRuby. TruffleRuby generally needs to force the RUBY platform
|
||||
# variant unless the name is explicitly allowlisted.
|
||||
|
||||
def default_force_ruby_platform
|
||||
return false unless RUBY_ENGINE == "truffleruby"
|
||||
|
||||
!Gem::Platform::REUSE_AS_BINARY_ON_TRUFFLERUBY.include?(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,127 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "vendored_thor"
|
||||
|
||||
module Bundler
|
||||
module FriendlyErrors
|
||||
module_function
|
||||
|
||||
def enable!
|
||||
@disabled = false
|
||||
end
|
||||
|
||||
def disabled?
|
||||
@disabled
|
||||
end
|
||||
|
||||
def disable!
|
||||
@disabled = true
|
||||
end
|
||||
|
||||
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
|
||||
when BundlerError
|
||||
if Bundler.ui.debug?
|
||||
Bundler.ui.trace error
|
||||
else
|
||||
Bundler.ui.error error.message, wrap: true
|
||||
end
|
||||
when Thor::Error
|
||||
Bundler.ui.error error.message
|
||||
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
|
||||
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.error <<~EOS, nil, nil
|
||||
--- ERROR REPORT TEMPLATE -------------------------------------------------------
|
||||
|
||||
```
|
||||
#{exception_message(e)}
|
||||
```
|
||||
|
||||
#{Bundler::Env.report}
|
||||
--- TEMPLATE END ----------------------------------------------------------------
|
||||
|
||||
EOS
|
||||
|
||||
Bundler.ui.error "Unfortunately, an unexpected error occurred, and Bundler cannot continue."
|
||||
|
||||
Bundler.ui.error <<~EOS, nil, :yellow
|
||||
|
||||
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 fill in the new issue form located at #{new_issue_url}. Make sure to copy and paste the full output of this command under the "What happened instead?" section.
|
||||
EOS
|
||||
end
|
||||
|
||||
def exception_message(error)
|
||||
message = serialized_exception_for(error)
|
||||
cause = error.cause
|
||||
return message unless cause
|
||||
|
||||
message + serialized_exception_for(cause)
|
||||
end
|
||||
|
||||
def serialized_exception_for(e)
|
||||
<<~EOS
|
||||
#{e.class}: #{e.message}
|
||||
#{e.backtrace&.join("\n ")&.chomp}
|
||||
EOS
|
||||
end
|
||||
|
||||
def issues_url(exception)
|
||||
message = exception.message.lines.first.tr(":", " ").chomp
|
||||
message = message.split("-").first if exception.is_a?(Errno)
|
||||
require "cgi/escape"
|
||||
require "cgi/util" unless defined?(CGI::EscapeExt)
|
||||
"https://github.com/ruby/rubygems/search?q=" \
|
||||
"#{CGI.escape(message)}&type=Issues"
|
||||
end
|
||||
|
||||
def new_issue_url
|
||||
"https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md"
|
||||
end
|
||||
end
|
||||
|
||||
def self.with_friendly_errors
|
||||
FriendlyErrors.enable!
|
||||
yield
|
||||
rescue SignalException
|
||||
raise
|
||||
rescue Exception => e # rubocop:disable Lint/RescueException
|
||||
raise if FriendlyErrors.disabled?
|
||||
|
||||
FriendlyErrors.log_error(e)
|
||||
exit FriendlyErrors.exit_status(e)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,237 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../bundler"
|
||||
require "shellwords"
|
||||
|
||||
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 tag_prefix=(prefix)
|
||||
instance.tag_prefix = prefix
|
||||
end
|
||||
|
||||
def gemspec(&block)
|
||||
gemspec = instance.gemspec
|
||||
block&.call(gemspec)
|
||||
gemspec
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :spec_path, :base, :gemspec
|
||||
|
||||
attr_writer :tag_prefix
|
||||
|
||||
def initialize(base = nil, name = nil)
|
||||
@base = File.expand_path(base || SharedHelpers.pwd)
|
||||
gemspecs = name ? [File.join(@base, "#{name}.gemspec")] : SharedHelpers.glob_files_in_dir("{,*}.gemspec", @base)
|
||||
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)
|
||||
@tag_prefix = ""
|
||||
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 "Generate SHA512 checksum of #{name}-#{version}.gem into the checksums directory."
|
||||
task "build:checksum" => "build" do
|
||||
build_checksum(built_gem_path)
|
||||
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" => "build" do
|
||||
rubygem_push(built_gem_path) if gem_push?
|
||||
end
|
||||
|
||||
GemHelper.instance = self
|
||||
end
|
||||
|
||||
def build_gem
|
||||
file_name = nil
|
||||
sh([*gem_command, "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
|
||||
cmd = [*gem_command, "install", built_gem_path.to_s]
|
||||
cmd << "--local" if local
|
||||
sh(cmd)
|
||||
Bundler.ui.confirm "#{name} (#{version}) installed."
|
||||
end
|
||||
|
||||
def build_checksum(built_gem_path = nil)
|
||||
built_gem_path ||= build_gem
|
||||
SharedHelpers.filesystem_access(File.join(base, "checksums")) {|p| FileUtils.mkdir_p(p) }
|
||||
file_name = "#{File.basename(built_gem_path)}.sha512"
|
||||
require "digest/sha2"
|
||||
checksum = ::Digest::SHA512.file(built_gem_path).hexdigest
|
||||
target = File.join(base, "checksums", file_name)
|
||||
File.write(target, checksum + "\n")
|
||||
Bundler.ui.confirm "#{name} #{version} checksum written to checksums/#{file_name}."
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def rubygem_push(path)
|
||||
cmd = [*gem_command, "push", path]
|
||||
cmd << "--key" << gem_key if gem_key
|
||||
cmd << "--host" << allowed_push_host if allowed_push_host
|
||||
sh_with_input(cmd)
|
||||
Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_push_host}"
|
||||
end
|
||||
|
||||
def built_gem_path
|
||||
SharedHelpers.glob_files_in_dir("#{name}-*.gem", base).sort_by {|f| File.mtime(f) }.last
|
||||
end
|
||||
|
||||
def git_push(remote = nil)
|
||||
remote ||= default_remote
|
||||
sh("git push #{remote} refs/heads/#{current_branch}".shellsplit)
|
||||
sh("git push #{remote} refs/tags/#{version_tag}".shellsplit)
|
||||
Bundler.ui.confirm "Pushed git commits and release tag."
|
||||
end
|
||||
|
||||
def default_remote
|
||||
remote_for_branch, status = sh_with_status(%W[git config --get branch.#{current_branch}.remote])
|
||||
return "origin" unless status.success?
|
||||
|
||||
remote_for_branch.strip
|
||||
end
|
||||
|
||||
def current_branch
|
||||
# We can replace this with `git branch --show-current` once we drop support for git < 2.22.0
|
||||
sh(%w[git rev-parse --abbrev-ref HEAD]).gsub(%r{\Aheads/}, "").strip
|
||||
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&.empty?
|
||||
|
||||
allowed_push_host || env_rubygems_host || "rubygems.org"
|
||||
end
|
||||
|
||||
def already_tagged?
|
||||
return false unless sh(%w[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_status(%w[git diff --exit-code])[1].success?
|
||||
end
|
||||
|
||||
def committed?
|
||||
sh_with_status(%w[git diff-index --quiet --cached HEAD])[1].success?
|
||||
end
|
||||
|
||||
def tag_version
|
||||
sh %W[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_status %W[git tag -d #{version_tag}]
|
||||
raise
|
||||
end
|
||||
|
||||
def version
|
||||
gemspec.version
|
||||
end
|
||||
|
||||
def version_tag
|
||||
"#{@tag_prefix}v#{version}"
|
||||
end
|
||||
|
||||
def name
|
||||
gemspec.name
|
||||
end
|
||||
|
||||
def sh_with_input(cmd)
|
||||
Bundler.ui.debug(cmd)
|
||||
SharedHelpers.chdir(base) do
|
||||
abort unless Kernel.system(*cmd)
|
||||
end
|
||||
end
|
||||
|
||||
def sh(cmd, &block)
|
||||
out, status = sh_with_status(cmd, &block)
|
||||
unless status.success?
|
||||
raise("Running `#{cmd.shelljoin}` failed with the following output:\n\n#{out}\n")
|
||||
end
|
||||
out
|
||||
end
|
||||
|
||||
def sh_with_status(cmd, &block)
|
||||
Bundler.ui.debug(cmd)
|
||||
SharedHelpers.chdir(base) do
|
||||
outbuf = IO.popen(cmd, err: [:child, :out], &:read)
|
||||
status = $?
|
||||
block&.call(outbuf) if status.success?
|
||||
[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
|
||||
|
||||
def gem_command
|
||||
ENV["GEM_COMMAND"]&.shellsplit || ["gem"]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rake/clean"
|
||||
CLOBBER.include "pkg"
|
||||
|
||||
require_relative "gem_helper"
|
||||
Bundler::GemHelper.install_tasks
|
||||
@@ -0,0 +1,147 @@
|
||||
# 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
|
||||
attr_reader :level
|
||||
attr_accessor :pre
|
||||
|
||||
# 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 be 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
|
||||
|
||||
# Creates a GemVersionPromoter instance.
|
||||
#
|
||||
# @return [GemVersionPromoter]
|
||||
def initialize
|
||||
@level = :major
|
||||
@strict = false
|
||||
@pre = false
|
||||
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 Resolver::Package and an Array of Specifications of available
|
||||
# versions for a gem, this method will return the Array of Specifications
|
||||
# sorted 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 package [Resolver::Package] The package being resolved.
|
||||
# @param specs [Specification] An array of Specifications for the package.
|
||||
# @return [Specification] A new instance of the Specification Array sorted.
|
||||
def sort_versions(package, specs)
|
||||
locked_version = package.locked_version
|
||||
|
||||
result = specs.sort do |a, b|
|
||||
unless package.prerelease_specified? || pre?
|
||||
a_pre = a.prerelease?
|
||||
b_pre = b.prerelease?
|
||||
|
||||
next 1 if a_pre && !b_pre
|
||||
next -1 if b_pre && !a_pre
|
||||
end
|
||||
|
||||
if major? || locked_version.nil?
|
||||
b <=> a
|
||||
elsif either_version_older_than_locked?(a, b, locked_version)
|
||||
b <=> a
|
||||
elsif segments_do_not_match?(a, b, :major)
|
||||
a <=> b
|
||||
elsif !minor? && segments_do_not_match?(a, b, :minor)
|
||||
a <=> b
|
||||
else
|
||||
b <=> a
|
||||
end
|
||||
end
|
||||
post_sort(result, package.unlock?, locked_version)
|
||||
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
|
||||
|
||||
# @return [bool] Convenience method for testing value of pre variable.
|
||||
def pre?
|
||||
pre == true
|
||||
end
|
||||
|
||||
# Given a Resolver::Package and an Array of Specifications of available
|
||||
# versions for a gem, this method will truncate the Array if strict
|
||||
# is true. That means filtering out downgrades from the version currently
|
||||
# locked, and filtering out upgrades that go past the selected level (major,
|
||||
# minor, or patch).
|
||||
# @param package [Resolver::Package] The package being resolved.
|
||||
# @param specs [Specification] An array of Specifications for the package.
|
||||
# @return [Specification] A new instance of the Specification Array
|
||||
# truncated.
|
||||
def filter_versions(package, specs)
|
||||
return specs unless strict
|
||||
|
||||
locked_version = package.locked_version
|
||||
return specs if locked_version.nil? || major?
|
||||
|
||||
specs.select do |spec|
|
||||
gsv = spec.version
|
||||
|
||||
must_match = minor? ? [0] : [0, 1]
|
||||
|
||||
all_match = must_match.all? {|idx| gsv.segments[idx] == locked_version.segments[idx] }
|
||||
all_match && gsv >= locked_version
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def either_version_older_than_locked?(a, b, locked_version)
|
||||
a.version < locked_version || b.version < locked_version
|
||||
end
|
||||
|
||||
def segments_do_not_match?(a, b, level)
|
||||
index = [:major, :minor].index(level)
|
||||
a.segments[index] != b.segments[index]
|
||||
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, unlock, locked_version)
|
||||
if unlock || locked_version.nil?
|
||||
result
|
||||
else
|
||||
move_version_to_beginning(result, locked_version)
|
||||
end
|
||||
end
|
||||
|
||||
def move_version_to_beginning(result, version)
|
||||
move, keep = result.partition {|s| s.version.to_s == version.to_s }
|
||||
move.concat(keep)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,203 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Index
|
||||
include Enumerable
|
||||
|
||||
def self.build
|
||||
i = new
|
||||
yield i
|
||||
i
|
||||
end
|
||||
|
||||
attr_reader :specs, :duplicates, :sources
|
||||
protected :specs, :duplicates
|
||||
|
||||
RUBY = "ruby"
|
||||
NULL = "\0"
|
||||
|
||||
def initialize
|
||||
@sources = []
|
||||
@cache = {}
|
||||
@specs = {}
|
||||
@duplicates = {}
|
||||
end
|
||||
|
||||
def initialize_copy(o)
|
||||
@sources = o.sources.dup
|
||||
@cache = {}
|
||||
@specs = {}
|
||||
@duplicates = {}
|
||||
|
||||
o.specs.each do |name, hash|
|
||||
@specs[name] = hash.dup
|
||||
end
|
||||
o.duplicates.each do |name, array|
|
||||
@duplicates[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
|
||||
|
||||
# Search this index's specs, and any source indexes that this index knows
|
||||
# about, returning all of the results.
|
||||
def search(query)
|
||||
results = local_search(query)
|
||||
return results unless @sources.any?
|
||||
|
||||
@sources.each do |source|
|
||||
results = safe_concat(results, source.search(query))
|
||||
end
|
||||
results.uniq!(&:full_name) unless results.empty? # avoid modifying frozen EMPTY_SEARCH
|
||||
results
|
||||
end
|
||||
|
||||
alias_method :[], :search
|
||||
|
||||
def local_search(query)
|
||||
case query
|
||||
when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query)
|
||||
when String then specs_by_name(query)
|
||||
when Array then specs_by_name_and_version(*query)
|
||||
else
|
||||
raise "You can't search for a #{query.inspect}."
|
||||
end
|
||||
end
|
||||
|
||||
def add(spec)
|
||||
(@specs[spec.name] ||= {}).store(spec.full_name, spec)
|
||||
end
|
||||
alias_method :<<, :add
|
||||
|
||||
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
|
||||
|
||||
def unmet_dependency_names
|
||||
dependency_names.select do |name|
|
||||
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
|
||||
|
||||
# Combines indexes proritizing existing specs, like `Hash#reverse_merge!`
|
||||
# Duplicate specs found in `other` are stored in `@duplicates`.
|
||||
def use(other)
|
||||
return unless other
|
||||
other.each do |spec|
|
||||
exist?(spec) ? add_duplicate(spec) : add(spec)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# Combines indexes proritizing specs from `other`, like `Hash#merge!`
|
||||
# Duplicate specs found in `self` are saved in `@duplicates`.
|
||||
def merge!(other)
|
||||
return unless other
|
||||
other.each do |spec|
|
||||
if existing = find_by_spec(spec)
|
||||
unless dependencies_eql?(existing, spec)
|
||||
Bundler.ui.warn "Local specification for #{spec.full_name} has different dependencies than the remote gem, ignoring it"
|
||||
next
|
||||
end
|
||||
|
||||
add_duplicate(existing)
|
||||
end
|
||||
add spec
|
||||
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
|
||||
def subset?(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.runtime_dependencies
|
||||
other_deps = other_spec.runtime_dependencies
|
||||
deps.sort == other_deps.sort
|
||||
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 safe_concat(a, b)
|
||||
return a if b.empty?
|
||||
return b if a.empty?
|
||||
a.concat(b)
|
||||
end
|
||||
|
||||
def add_duplicate(spec)
|
||||
(@duplicates[spec.name] ||= []) << spec
|
||||
end
|
||||
|
||||
def specs_by_name_and_version(name, version)
|
||||
results = @specs[name]&.values
|
||||
return EMPTY_SEARCH unless results
|
||||
results.select! {|spec| spec.version == version }
|
||||
results
|
||||
end
|
||||
|
||||
def specs_by_name(name)
|
||||
@specs[name]&.values || EMPTY_SEARCH
|
||||
end
|
||||
|
||||
EMPTY_SEARCH = [].freeze
|
||||
|
||||
def search_by_spec(spec)
|
||||
spec = find_by_spec(spec)
|
||||
spec ? [spec] : EMPTY_SEARCH
|
||||
end
|
||||
|
||||
def find_by_spec(spec)
|
||||
@specs[spec.name]&.fetch(spec.full_name, nil)
|
||||
end
|
||||
|
||||
def exist?(spec)
|
||||
@specs[spec.name]&.key?(spec.full_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,284 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Injector
|
||||
INJECTED_GEMS = "injected gems"
|
||||
|
||||
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)
|
||||
Bundler.definition.ensure_equivalent_gemfile_and_lockfile(true)
|
||||
|
||||
# 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.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
|
||||
|
||||
# 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)} was removed." }
|
||||
end
|
||||
|
||||
# Invalidate the cached Bundler.definition.
|
||||
# This prevents e.g. `bundle remove ...` from using outdated information.
|
||||
Bundler.reset_paths!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conservative_version(spec)
|
||||
version = spec.version
|
||||
return ">= 0" if version.nil?
|
||||
seg_end_index = version >= Gem::Version.new("1.0") ? 1 : 2
|
||||
|
||||
prerelease_suffix = version.to_s.delete_prefix(version.release.to_s) if version.prerelease?
|
||||
"#{version_prefix}#{version.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?
|
||||
path = ", path: \"#{d.path}\"" unless d.path.nil?
|
||||
git = ", git: \"#{d.git}\"" unless d.git.nil?
|
||||
github = ", github: \"#{d.github}\"" unless d.github.nil?
|
||||
branch = ", branch: \"#{d.branch}\"" unless d.branch.nil?
|
||||
ref = ", ref: \"#{d.ref}\"" unless d.ref.nil?
|
||||
glob = ", glob: \"#{d.glob}\"" unless d.glob.nil?
|
||||
require_path = ", require: #{convert_autorequire(d.autorequire)}" unless d.autorequire.nil?
|
||||
|
||||
%(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{glob}#{require_path})
|
||||
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
|
||||
|
||||
# evaluates a gemfile to remove the specified gem
|
||||
# from it.
|
||||
def remove_deps(gemfile_path)
|
||||
initial_gemfile = File.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 operation if no gems were removed
|
||||
# no need to operate on gemfile further
|
||||
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] gemfile_path Path of the Gemfile.
|
||||
# @return [Array] 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.*\)/
|
||||
new_gemfile = []
|
||||
multiline_removal = false
|
||||
File.readlines(gemfile_path).each do |line|
|
||||
match_data = line.match(patterns)
|
||||
if match_data && is_not_within_comment?(line, match_data)
|
||||
multiline_removal = line.rstrip.end_with?(",")
|
||||
# skip lines which match the regex
|
||||
next
|
||||
end
|
||||
|
||||
# skip followup lines until line does not end with ','
|
||||
new_gemfile << line unless multiline_removal
|
||||
multiline_removal = line.rstrip.end_with?(",") if multiline_removal
|
||||
end
|
||||
|
||||
# remove line \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 [String] line Individual line of gemfile content.
|
||||
# @param [MatchData] match_data Data about Regex match.
|
||||
def is_not_within_comment?(line, match_data)
|
||||
match_start_index = match_data.offset(0).first
|
||||
!line[0..match_start_index].include?("#")
|
||||
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.strip.start_with?(block_name)
|
||||
if /^\s*end\s*$/.match?(gemfile[index + 1])
|
||||
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)
|
||||
# evaluate 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
|
||||
|
||||
def convert_autorequire(autorequire)
|
||||
autorequire = autorequire.first
|
||||
return autorequire if autorequire == "false"
|
||||
autorequire.inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,106 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Allows for declaring a Gemfile inline in a ruby script, 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 force_latest_compatible [Boolean] Force installing the *latest*
|
||||
# compatible versions of the gems,
|
||||
# even if compatible versions are
|
||||
# already installed locally.
|
||||
# This also logs output if the
|
||||
# `:quiet` option is not set.
|
||||
# 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(force_latest_compatible = false, options = {}, &gemfile)
|
||||
require_relative "../bundler"
|
||||
Bundler.reset!
|
||||
|
||||
opts = options.dup
|
||||
ui = opts.delete(:ui) { Bundler::UI::Shell.new }
|
||||
ui.level = "silent" if opts.delete(:quiet) || !force_latest_compatible
|
||||
Bundler.ui = ui
|
||||
raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty?
|
||||
|
||||
old_gemfile = ENV["BUNDLE_GEMFILE"]
|
||||
old_lockfile = ENV["BUNDLE_LOCKFILE"]
|
||||
|
||||
Bundler.unbundle_env!
|
||||
|
||||
begin
|
||||
Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir))
|
||||
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile"
|
||||
Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock"
|
||||
|
||||
Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins]
|
||||
builder = Bundler::Dsl.new
|
||||
builder.instance_eval(&gemfile)
|
||||
|
||||
Bundler.settings.temporary(deployment: false, frozen: false) do
|
||||
definition = builder.to_definition(nil, true)
|
||||
definition.validate_runtime!
|
||||
|
||||
if force_latest_compatible || definition.missing_specs?
|
||||
Bundler.settings.temporary(inline: true, no_install: false) 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
|
||||
|
||||
begin
|
||||
runtime = Bundler::Runtime.new(nil, definition).setup
|
||||
rescue Gem::LoadError => e
|
||||
name = e.name
|
||||
version = e.requirement.requirements.first[1]
|
||||
activated_version = Gem.loaded_specs[name].version
|
||||
|
||||
Bundler.ui.info \
|
||||
"The #{name} gem was resolved to #{version}, but #{activated_version} was activated by Bundler while installing it, causing a conflict. " \
|
||||
"Bundler will now retry resolving with #{activated_version} instead."
|
||||
|
||||
builder.dependencies.delete_if {|d| d.name == name }
|
||||
builder.instance_eval { gem name, activated_version }
|
||||
definition = builder.to_definition(nil, true)
|
||||
|
||||
retry
|
||||
end
|
||||
|
||||
runtime.require
|
||||
end
|
||||
ensure
|
||||
if old_gemfile
|
||||
ENV["BUNDLE_GEMFILE"] = old_gemfile
|
||||
else
|
||||
ENV["BUNDLE_GEMFILE"] = ""
|
||||
end
|
||||
|
||||
if old_lockfile
|
||||
ENV["BUNDLE_LOCKFILE"] = old_lockfile
|
||||
else
|
||||
ENV["BUNDLE_LOCKFILE"] = ""
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,247 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "worker"
|
||||
require_relative "installer/parallel_installer"
|
||||
require_relative "installer/standalone"
|
||||
require_relative "installer/gem_installer"
|
||||
|
||||
module Bundler
|
||||
class Installer
|
||||
attr_reader :post_install_messages, :definition
|
||||
|
||||
# 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)
|
||||
Bundler.create_bundle_path
|
||||
|
||||
ProcessLock.lock do
|
||||
# Invalidate any stale gem specification cache from before we acquired the lock.
|
||||
# Another process may have installed gems while we were waiting.
|
||||
Gem::Specification.reset
|
||||
@definition.sources.clear_cache
|
||||
|
||||
@definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])
|
||||
|
||||
if @definition.dependencies.empty?
|
||||
Bundler.ui.warn "The Gemfile specifies no dependencies"
|
||||
lock
|
||||
return
|
||||
end
|
||||
|
||||
if @definition.setup_domain!(options)
|
||||
ensure_specs_are_compatible!
|
||||
load_plugins
|
||||
end
|
||||
install(options)
|
||||
|
||||
Gem::Specification.reset # invalidate gem specification cache so that installed gems are immediately available
|
||||
# Drop the source caches too, since releasing resolution memory dropped
|
||||
# the source indexes. A resolution happening after this point would
|
||||
# otherwise rebuild them around a snapshot of installed gems taken
|
||||
# before the install, and materialize against remote specs that don't
|
||||
# know where the gems they stand for ended up.
|
||||
@definition.sources.clear_cache
|
||||
|
||||
lock
|
||||
Standalone.new(options[:standalone], @definition).generate if options[:standalone]
|
||||
end
|
||||
end
|
||||
|
||||
def generate_bundler_executable_stubs(spec, options = {})
|
||||
if spec.name == "bundler"
|
||||
Bundler.ui.warn "Bundler itself does not use binstubs because its version is selected by RubyGems"
|
||||
return
|
||||
end
|
||||
|
||||
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", __dir__)
|
||||
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
|
||||
|
||||
mode = Gem.win_platform? ? "wb:UTF-8" : "w"
|
||||
require "erb"
|
||||
content = ERB.new(template, trim_mode: "-").result(binding)
|
||||
|
||||
File.write(binstub_path, content, mode: mode, perm: 0o777 & ~File.umask)
|
||||
if Gem.win_platform? || options[:all_platforms]
|
||||
prefix = "@ruby -x \"%~f0\" %*\n@exit /b %ERRORLEVEL%\n\n"
|
||||
File.write("#{binstub_path}.cmd", prefix + content, mode: mode)
|
||||
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, options = {})
|
||||
# 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", __dir__))
|
||||
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
|
||||
|
||||
mode = Gem.win_platform? ? "wb:UTF-8" : "w"
|
||||
require "erb"
|
||||
content = ERB.new(template, trim_mode: "-").result(binding)
|
||||
|
||||
File.write("#{bin_path}/#{executable}", content, mode: mode, perm: 0o755)
|
||||
if Gem.win_platform? || options[:all_platforms]
|
||||
prefix = "@ruby -x \"%~f0\" %*\n@exit /b %ERRORLEVEL%\n\n"
|
||||
File.write("#{bin_path}/#{executable}.cmd", prefix + content, mode: mode)
|
||||
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)
|
||||
standalone = options[:standalone]
|
||||
force = options[:force]
|
||||
local = options[:local] || options[:"prefer-local"]
|
||||
jobs = Bundler.settings.installation_parallelization
|
||||
specs = @definition.specs
|
||||
# Installing default gems may need the remote index again to cache
|
||||
# their .gem files, so keep resolution memory around in that case.
|
||||
# The bundler spec itself is excluded because it comes from the
|
||||
# metadata source and never goes through that path.
|
||||
@definition.release_resolution_memory! if specs.none? {|s| s.default_gem? && s.source.is_a?(Source::Rubygems) }
|
||||
spec_installations = ParallelInstaller.call(self, specs, jobs, standalone, force, local: local)
|
||||
spec_installations.each do |installation|
|
||||
post_install_messages[installation.name] = installation.post_install_message if installation.has_post_install_message?
|
||||
end
|
||||
end
|
||||
|
||||
def load_plugins
|
||||
Gem.load_plugins
|
||||
|
||||
requested_path_gems = @definition.specs.select {|s| s.source.is_a?(Source::Path) }
|
||||
path_plugin_files = requested_path_gems.flat_map do |spec|
|
||||
spec.matches_for_glob("rubygems_plugin#{Bundler.rubygems.suffix_pattern}")
|
||||
rescue TypeError
|
||||
error_message = "#{spec.name} #{spec.version} has an invalid gemspec"
|
||||
raise Gem::InvalidSpecificationException, error_message
|
||||
end
|
||||
Gem.load_plugin_files(path_plugin_files)
|
||||
Gem.load_env_plugins
|
||||
end
|
||||
|
||||
def ensure_specs_are_compatible!
|
||||
@definition.specs.each do |spec|
|
||||
unless spec.matches_current_ruby?
|
||||
raise InstallError, "#{spec.full_name} requires ruby version #{spec.required_ruby_version}, " \
|
||||
"which is incompatible with the current version, #{Gem.ruby_version}"
|
||||
end
|
||||
unless spec.matches_current_rubygems?
|
||||
raise InstallError, "#{spec.full_name} requires rubygems version #{spec.required_rubygems_version}, " \
|
||||
"which is incompatible with the current version, #{Gem.rubygems_version}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def lock
|
||||
@definition.lock
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class GemInstaller
|
||||
attr_reader :spec, :standalone, :worker, :force, :local, :installer
|
||||
|
||||
def initialize(spec, installer, standalone = false, worker = 0, force = false, local = false)
|
||||
@spec = spec
|
||||
@installer = installer
|
||||
@standalone = standalone
|
||||
@worker = worker
|
||||
@force = force
|
||||
@local = local
|
||||
end
|
||||
|
||||
def install_from_spec
|
||||
post_install_message = install
|
||||
Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
|
||||
[true, post_install_message]
|
||||
rescue Bundler::InstallHookError, Bundler::SecurityError, Bundler::APIResponseMismatchError, Bundler::InsecureInstallPathError
|
||||
raise
|
||||
rescue Errno::ENOSPC
|
||||
[false, out_of_space_message]
|
||||
rescue Bundler::BundlerError, Gem::InstallError => e
|
||||
[false, specific_failure_message(e)]
|
||||
end
|
||||
|
||||
def download
|
||||
spec.source.download(
|
||||
spec,
|
||||
force: force,
|
||||
local: local,
|
||||
build_args: Array(spec_settings),
|
||||
previous_spec: previous_spec,
|
||||
)
|
||||
|
||||
[true, nil]
|
||||
rescue Bundler::BundlerError => e
|
||||
[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"
|
||||
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
|
||||
install_error_message
|
||||
end
|
||||
|
||||
def install_error_message
|
||||
"An error occurred while installing #{spec.name} (#{spec.version}), and Bundler cannot continue."
|
||||
end
|
||||
|
||||
def spec_settings
|
||||
# Fetch the build settings, if there are any
|
||||
if settings = Bundler.settings["build.#{spec.name}"]
|
||||
require "shellwords"
|
||||
Shellwords.shellsplit(settings)
|
||||
end
|
||||
end
|
||||
|
||||
def install
|
||||
spec.source.install(
|
||||
spec,
|
||||
force: force,
|
||||
local: local,
|
||||
build_args: Array(spec_settings),
|
||||
previous_spec: previous_spec,
|
||||
)
|
||||
end
|
||||
|
||||
def previous_spec
|
||||
locked_gems = installer.definition.locked_gems
|
||||
return unless locked_gems
|
||||
|
||||
locked_gems.specs.find {|s| s.name == spec.name }
|
||||
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
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,294 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../worker"
|
||||
require_relative "gem_installer"
|
||||
|
||||
module Bundler
|
||||
class ParallelInstaller
|
||||
class SpecInstallation
|
||||
attr_accessor :spec, :name, :full_name, :post_install_message, :state, :error, :dependencies
|
||||
def initialize(spec)
|
||||
@spec = spec
|
||||
@name = spec.name
|
||||
@full_name = spec.full_name
|
||||
@state = :none
|
||||
@post_install_message = ""
|
||||
@error = nil
|
||||
end
|
||||
|
||||
def installed?
|
||||
state == :installed
|
||||
end
|
||||
|
||||
def enqueued?
|
||||
state == :enqueued
|
||||
end
|
||||
|
||||
def enqueue_with_priority?
|
||||
state == :installable && spec.extensions.any?
|
||||
end
|
||||
|
||||
def failed?
|
||||
state == :failed
|
||||
end
|
||||
|
||||
def ready_to_enqueue?
|
||||
state == :none
|
||||
end
|
||||
|
||||
def ready_to_install?(installed_specs)
|
||||
return false unless state == :downloaded
|
||||
|
||||
spec.extensions.none? || dependencies_installed?(installed_specs)
|
||||
end
|
||||
|
||||
def has_post_install_message?
|
||||
!post_install_message.empty?
|
||||
end
|
||||
|
||||
# Recursively checks that all dependencies (direct and transitive) have been installed.
|
||||
def dependencies_installed?(installed_specs)
|
||||
dependencies.all? do |dep|
|
||||
installed_specs.include?(dep.name) && dep.dependencies_installed?(installed_specs)
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#<#{self.class} #{full_name} (#{state})>"
|
||||
end
|
||||
end
|
||||
|
||||
def self.call(*args, **kwargs)
|
||||
new(*args, **kwargs).call
|
||||
end
|
||||
|
||||
attr_reader :size
|
||||
|
||||
def initialize(installer, all_specs, size, standalone, force, local: false, skip: nil)
|
||||
@installer = installer
|
||||
@size = size
|
||||
@standalone = standalone
|
||||
@force = force
|
||||
@local = local
|
||||
@specs = all_specs.map {|s| SpecInstallation.new(s) }
|
||||
specs_by_name = @specs.to_h {|s| [s.name, s] }
|
||||
@specs.each do |spec_install|
|
||||
spec_install.dependencies = spec_install.spec.dependencies.filter_map do |dep|
|
||||
specs_by_name[dep.name] unless dep.type == :development || dep.name == spec_install.name
|
||||
end
|
||||
end
|
||||
@specs.each do |spec_install|
|
||||
spec_install.state = :installed if skip.include?(spec_install.name)
|
||||
end if skip
|
||||
@spec_set = all_specs
|
||||
@rake = @specs.find {|s| s.name == "rake" unless s.installed? }
|
||||
end
|
||||
|
||||
def call
|
||||
if @rake
|
||||
do_download(@rake, 0)
|
||||
do_install(@rake, 0)
|
||||
Gem::Specification.reset
|
||||
end
|
||||
|
||||
if @size > 1
|
||||
install_with_worker
|
||||
else
|
||||
install_serially
|
||||
end
|
||||
|
||||
handle_error if failed_specs.any?
|
||||
@specs
|
||||
ensure
|
||||
worker_pool&.stop
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def failed_specs
|
||||
@specs.select(&:failed?)
|
||||
end
|
||||
|
||||
def install_with_worker
|
||||
with_jobserver do
|
||||
installed_specs = {}
|
||||
enqueue_specs(installed_specs)
|
||||
|
||||
process_specs(installed_specs) until finished_installing?
|
||||
end
|
||||
end
|
||||
|
||||
def with_jobserver
|
||||
# The jobserver hands tokens to child `make` processes through MAKEFLAGS
|
||||
# using the GNU make `--jobserver-auth` protocol. nmake, the default make
|
||||
# on mswin, instead reads MAKEFLAGS as bare option letters and aborts
|
||||
# every native extension build with `fatal error U1065: invalid option
|
||||
# '-'`. Skip the jobserver when nmake is in use. Other Windows toolchains
|
||||
# such as mingw use GNU make and keep working through the inherited pipe.
|
||||
return yield if nmake? || bsd_make?
|
||||
|
||||
begin
|
||||
r, w = IO.pipe
|
||||
r.close_on_exec = false
|
||||
w.close_on_exec = false
|
||||
w.write("*" * @size)
|
||||
|
||||
old_makeflags = ENV["MAKEFLAGS"]
|
||||
ENV["MAKEFLAGS"] = [old_makeflags, "--jobserver-auth=#{r.fileno},#{w.fileno}"].compact.join(" ")
|
||||
|
||||
yield
|
||||
ensure
|
||||
# Restore MAKEFLAGS before closing the pipe so a close failure can't
|
||||
# leave the process with descriptors that point at a closed pipe.
|
||||
old_makeflags ? ENV["MAKEFLAGS"] = old_makeflags : ENV.delete("MAKEFLAGS")
|
||||
|
||||
r&.close
|
||||
w&.close
|
||||
end
|
||||
end
|
||||
|
||||
# Mirror how RubyGems' extension builder picks the make program so the
|
||||
# jobserver is only set up when a GNU-compatible make will consume it.
|
||||
def nmake?
|
||||
make = ENV["MAKE"] || ENV["make"]
|
||||
make ||= "nmake" if RUBY_PLATFORM.include?("mswin")
|
||||
/\bnmake/i.match?(make.to_s)
|
||||
end
|
||||
|
||||
def bsd_make?
|
||||
return false unless Gem.freebsd_platform?
|
||||
make = ENV["MAKE"] || ENV["make"] || "make"
|
||||
!/\bgmake/i.match?(make)
|
||||
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_download(spec_install, 0)
|
||||
do_install(spec_install, 0)
|
||||
end
|
||||
end
|
||||
|
||||
def worker_pool
|
||||
@worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda {|spec_install, worker_num|
|
||||
case spec_install.state
|
||||
when :enqueued
|
||||
do_download(spec_install, worker_num)
|
||||
when :installable
|
||||
do_install(spec_install, worker_num)
|
||||
else
|
||||
spec_install
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def do_download(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, @local
|
||||
)
|
||||
|
||||
success, message = gem_installer.download
|
||||
|
||||
if success
|
||||
spec_install.state = :downloaded
|
||||
else
|
||||
spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
|
||||
spec_install.state = :failed
|
||||
end
|
||||
|
||||
spec_install
|
||||
end
|
||||
|
||||
def do_install(spec_install, worker_num)
|
||||
gem_installer = Bundler::GemInstaller.new(
|
||||
spec_install.spec, @installer, @standalone, worker_num, @force, @local
|
||||
)
|
||||
success, message = gem_installer.install_from_spec
|
||||
if success
|
||||
spec_install.state = :installed
|
||||
spec_install.post_install_message = message unless message.nil?
|
||||
else
|
||||
spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
|
||||
spec_install.state = :failed
|
||||
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(installed_specs)
|
||||
spec = worker_pool.deq
|
||||
|
||||
if spec.installed?
|
||||
installed_specs[spec.name] = true
|
||||
return
|
||||
elsif spec.failed?
|
||||
return
|
||||
elsif spec.ready_to_install?(installed_specs)
|
||||
spec.state = :installable
|
||||
end
|
||||
|
||||
worker_pool.enq(spec, priority: spec.enqueue_with_priority?)
|
||||
end
|
||||
|
||||
def finished_installing?
|
||||
@specs.all? do |spec|
|
||||
return true if spec.failed?
|
||||
spec.installed?
|
||||
end
|
||||
end
|
||||
|
||||
def handle_error
|
||||
errors = failed_specs.map(&:error)
|
||||
if exception = errors.find {|e| e.is_a?(Bundler::BundlerError) }
|
||||
raise exception
|
||||
end
|
||||
raise Bundler::InstallError, errors.join("\n\n")
|
||||
end
|
||||
|
||||
def require_tree_for_spec(spec)
|
||||
tree = @spec_set.what_required(spec)
|
||||
gemfile_name = begin
|
||||
File.basename(SharedHelpers.default_gemfile)
|
||||
rescue GemfileNotFound
|
||||
# This runs while reporting an install error. When no Gemfile can be
|
||||
# located (e.g. Bundler used as a library), raising here would mask
|
||||
# the original error, so fall back to a generic header instead.
|
||||
"Gemfile"
|
||||
end
|
||||
t = String.new("In #{gemfile_name}:\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(installed_specs)
|
||||
@specs.each do |spec|
|
||||
if spec.installed?
|
||||
installed_specs[spec.name] = true
|
||||
next
|
||||
end
|
||||
|
||||
spec.state = :enqueued
|
||||
worker_pool.enq spec
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Standalone
|
||||
def initialize(groups, definition)
|
||||
@specs = definition.specs_for(groups)
|
||||
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 prevent_gem_activation
|
||||
file.puts define_path_helpers
|
||||
file.puts reverse_rubygems_kernel_mixin
|
||||
paths.each do |path|
|
||||
if Pathname.new(path).absolute?
|
||||
file.puts %($:.unshift "#{path}")
|
||||
else
|
||||
file.puts %($:.unshift File.expand_path("\#{__dir__}/#{path}"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def paths
|
||||
@specs.flat_map do |spec|
|
||||
next if spec.name == "bundler"
|
||||
Array(spec.require_paths).map do |path|
|
||||
gem_path(path, spec).
|
||||
sub(version_dir, '#{RUBY_ENGINE}/#{Gem.ruby_api_version}').
|
||||
sub(extensions_dir, 'extensions/\k<platform>/#{Gem.extension_api_version}')
|
||||
# This is a static string intentionally. It's interpolated at a later time.
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
||||
def version_dir
|
||||
"#{RUBY_ENGINE}/#{Gem.ruby_api_version}"
|
||||
end
|
||||
|
||||
def extensions_dir
|
||||
%r{extensions/(?<platform>[^/]+)/#{Regexp.escape(Gem.extension_api_version)}}
|
||||
end
|
||||
|
||||
def bundler_path
|
||||
Bundler.root.join(Bundler.settings[:path].to_s, "bundler")
|
||||
end
|
||||
|
||||
def gem_path(path, spec)
|
||||
full_path = Pathname.new(path).absolute? ? path : File.join(spec.full_gem_path, path)
|
||||
if spec.source.instance_of?(Source::Path) && spec.source.path.absolute?
|
||||
full_path
|
||||
else
|
||||
SharedHelpers.relative_path_to(full_path, from: Bundler.root.join(bundler_path))
|
||||
end
|
||||
end
|
||||
|
||||
def prevent_gem_activation
|
||||
<<~'END'
|
||||
module Kernel
|
||||
remove_method(:gem) if private_method_defined?(:gem)
|
||||
|
||||
def gem(*)
|
||||
end
|
||||
|
||||
private :gem
|
||||
end
|
||||
END
|
||||
end
|
||||
|
||||
def define_path_helpers
|
||||
<<~'END'
|
||||
unless defined?(Gem)
|
||||
module Gem
|
||||
def self.ruby_api_version
|
||||
RbConfig::CONFIG["ruby_version"]
|
||||
end
|
||||
|
||||
def self.extension_api_version
|
||||
if 'no' == RbConfig::CONFIG['ENABLE_SHARED']
|
||||
"#{ruby_api_version}-static"
|
||||
else
|
||||
ruby_api_version
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
END
|
||||
end
|
||||
|
||||
def reverse_rubygems_kernel_mixin
|
||||
<<~END
|
||||
if Gem.respond_to?(:discover_gems_on_require=)
|
||||
Gem.discover_gems_on_require = false
|
||||
else
|
||||
[::Kernel.singleton_class, ::Kernel].each do |k|
|
||||
if k.private_method_defined?(:gem_original_require)
|
||||
private_require = k.private_method_defined?(:require)
|
||||
k.send(:remove_method, :require)
|
||||
k.send(:define_method, :require, k.instance_method(:gem_original_require))
|
||||
k.send(:private, :require) if private_require
|
||||
end
|
||||
end
|
||||
end
|
||||
END
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,270 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "force_platform"
|
||||
|
||||
module Bundler
|
||||
class LazySpecification
|
||||
include MatchMetadata
|
||||
include MatchPlatform
|
||||
include ForcePlatform
|
||||
|
||||
attr_reader :name, :version, :platform, :materialization
|
||||
attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version
|
||||
|
||||
#
|
||||
# For backwards compatibility with existing lockfiles, if the most specific
|
||||
# locked platform is not a specific platform like x86_64-linux or
|
||||
# universal-java-11, then we keep the previous behaviour of resolving the
|
||||
# best platform variant at materiliazation time. For previous bundler
|
||||
# versions (before 2.2.0) this was always the case (except when the lockfile
|
||||
# only included non-ruby platforms), but we're also keeping this behaviour
|
||||
# on newer bundlers unless users generate the lockfile from scratch or
|
||||
# explicitly add a more specific platform.
|
||||
#
|
||||
attr_accessor :most_specific_locked_platform
|
||||
|
||||
alias_method :runtime_dependencies, :dependencies
|
||||
|
||||
def self.from_spec(s)
|
||||
lazy_spec = new(s.name, s.version, s.platform, s.source)
|
||||
lazy_spec.dependencies = s.runtime_dependencies
|
||||
lazy_spec.required_ruby_version = s.required_ruby_version
|
||||
lazy_spec.required_rubygems_version = s.required_rubygems_version
|
||||
lazy_spec
|
||||
end
|
||||
|
||||
def initialize(name, version, platform, source = nil, **materialization_options)
|
||||
@name = name
|
||||
@version = version
|
||||
@dependencies = []
|
||||
@required_ruby_version = Gem::Requirement.default
|
||||
@required_rubygems_version = Gem::Requirement.default
|
||||
@platform = platform || Gem::Platform::RUBY
|
||||
|
||||
@original_source = source
|
||||
@source = source
|
||||
@materialization_options = materialization_options
|
||||
|
||||
@force_ruby_platform = default_force_ruby_platform
|
||||
@most_specific_locked_platform = nil
|
||||
@materialization = nil
|
||||
end
|
||||
|
||||
def missing?
|
||||
@materialization == self
|
||||
end
|
||||
|
||||
def incomplete?
|
||||
@materialization.nil?
|
||||
end
|
||||
|
||||
def source_changed?
|
||||
@original_source != source
|
||||
end
|
||||
|
||||
def full_name
|
||||
@full_name ||= if platform == Gem::Platform::RUBY
|
||||
"#{@name}-#{@version}"
|
||||
else
|
||||
"#{@name}-#{@version}-#{platform}"
|
||||
end
|
||||
end
|
||||
|
||||
def lock_name
|
||||
@lock_name ||= name_tuple.lock_name
|
||||
end
|
||||
|
||||
def name_tuple
|
||||
Gem::NameTuple.new(@name, @version, @platform)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
full_name == other.full_name
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
full_name.eql?(other.full_name)
|
||||
end
|
||||
|
||||
def hash
|
||||
full_name.hash
|
||||
end
|
||||
|
||||
##
|
||||
# Does this locked specification satisfy +dependency+?
|
||||
#
|
||||
# NOTE: Rubygems default requirement is ">= 0", which doesn't match
|
||||
# prereleases of 0 versions, like "0.0.0.dev" or "0.0.0.SNAPSHOT". However,
|
||||
# bundler users expect those to work. We need to make sure that Gemfile
|
||||
# dependencies without explicit requirements (which use ">= 0" under the
|
||||
# hood by default) are still valid for locked specs using this kind of
|
||||
# versions. The method implements an ad-hoc fix for that. A better solution
|
||||
# might be to change default rubygems requirement of dependencies to be ">=
|
||||
# 0.A" but that's a major refactoring likely to break things. Hopefully we
|
||||
# can attempt it in the future.
|
||||
#
|
||||
|
||||
def satisfies?(dependency)
|
||||
effective_requirement = dependency.requirement == Gem::Requirement.default ? Gem::Requirement.new(">= 0.A") : dependency.requirement
|
||||
|
||||
@name == dependency.name && effective_requirement.satisfied_by?(Gem::Version.new(@version))
|
||||
end
|
||||
|
||||
def to_lock
|
||||
out = String.new
|
||||
out << " #{lock_name}\n"
|
||||
|
||||
dependencies.sort_by(&:to_s).uniq.each do |dep|
|
||||
next if dep.type == :development
|
||||
out << " #{dep.to_lock}\n"
|
||||
end
|
||||
|
||||
out
|
||||
end
|
||||
|
||||
def materialize_for_cache
|
||||
source.remote!
|
||||
|
||||
materialize(self, &:first)
|
||||
end
|
||||
|
||||
def materialized_for_installation
|
||||
@materialization = materialize_for_installation
|
||||
|
||||
self unless incomplete?
|
||||
end
|
||||
|
||||
def materialize_for_installation
|
||||
source.local!
|
||||
|
||||
if use_exact_resolved_specifications?
|
||||
spec = materialize(self) {|specs| choose_compatible(specs, fallback_to_non_installable: false) }
|
||||
return spec if spec
|
||||
|
||||
# Exact spec is incompatible; in frozen mode, try to find a compatible platform variant
|
||||
# In non-frozen mode, return nil to trigger re-resolution and lockfile update
|
||||
if Bundler.frozen_bundle?
|
||||
materialize([name, version]) {|specs| resolve_best_platform(specs) }
|
||||
end
|
||||
else
|
||||
materialize([name, version]) {|specs| resolve_best_platform(specs) }
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} @name=\"#{name}\" (#{full_name.delete_prefix("#{name}-")})>"
|
||||
end
|
||||
|
||||
def to_s
|
||||
lock_name
|
||||
end
|
||||
|
||||
def git_version
|
||||
return unless source.is_a?(Bundler::Source::Git)
|
||||
" #{source.revision[0..6]}"
|
||||
end
|
||||
|
||||
def force_ruby_platform!
|
||||
@force_ruby_platform = true
|
||||
end
|
||||
|
||||
def replace_source_with!(gemfile_source)
|
||||
return unless gemfile_source.can_lock?(self)
|
||||
|
||||
@source = gemfile_source
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def use_exact_resolved_specifications?
|
||||
!source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform?
|
||||
end
|
||||
|
||||
# Try platforms in order of preference until finding a compatible spec.
|
||||
# Used for legacy lockfiles and as a fallback when the exact locked spec
|
||||
# is incompatible. Falls back to frozen bundle behavior if none match.
|
||||
def resolve_best_platform(specs)
|
||||
find_compatible_platform_spec(specs) || frozen_bundle_fallback(specs)
|
||||
end
|
||||
|
||||
def find_compatible_platform_spec(specs)
|
||||
candidate_platforms.each do |plat|
|
||||
candidates = MatchPlatform.select_best_platform_match(specs, plat)
|
||||
spec = choose_compatible(candidates, fallback_to_non_installable: false)
|
||||
return spec if spec
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Platforms to try in order of preference. Ruby platform is last since it
|
||||
# requires compilation, but works when precompiled gems are incompatible.
|
||||
def candidate_platforms
|
||||
target = source.is_a?(Source::Path) ? platform : Bundler.local_platform
|
||||
[target, platform, Gem::Platform::RUBY].uniq
|
||||
end
|
||||
|
||||
# In frozen mode, accept any candidate. Will error at install time.
|
||||
# When target differs from locked platform, prefer locked platform's candidates
|
||||
# to preserve lockfile integrity.
|
||||
def frozen_bundle_fallback(specs)
|
||||
target = source.is_a?(Source::Path) ? platform : Bundler.local_platform
|
||||
fallback_platform = target == platform ? target : platform
|
||||
candidates = MatchPlatform.select_best_platform_match(specs, fallback_platform)
|
||||
choose_compatible(candidates)
|
||||
end
|
||||
|
||||
def ruby_platform_materializes_to_ruby_platform?
|
||||
generic_platform = Bundler.generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY
|
||||
|
||||
(most_specific_locked_platform != generic_platform) || force_ruby_platform || Bundler.settings[:force_ruby_platform]
|
||||
end
|
||||
|
||||
def materialize(query)
|
||||
matching_specs = source.specs.search(query)
|
||||
return self if matching_specs.empty?
|
||||
|
||||
yield matching_specs
|
||||
end
|
||||
|
||||
# If in frozen mode, we fallback to a non-installable candidate because by
|
||||
# doing this we avoid re-resolving and potentially end up changing the
|
||||
# lockfile, which is not allowed. In that case, we will give a proper error
|
||||
# about the mismatch higher up the stack, right before trying to install the
|
||||
# bad gem.
|
||||
def choose_compatible(candidates, fallback_to_non_installable: Bundler.frozen_bundle?)
|
||||
search = candidates.reverse.find do |spec|
|
||||
spec.is_a?(StubSpecification) || spec.matches_current_metadata?
|
||||
end
|
||||
if search.nil? && fallback_to_non_installable
|
||||
search = candidates.last
|
||||
end
|
||||
|
||||
if search
|
||||
validate_dependencies(search) if search.platform == platform
|
||||
|
||||
search.locked_platform = platform if search.instance_of?(RemoteSpecification) || search.instance_of?(EndpointSpecification)
|
||||
end
|
||||
search
|
||||
end
|
||||
|
||||
# Validate dependencies of this locked spec are consistent with dependencies
|
||||
# of the actual spec that was materialized.
|
||||
#
|
||||
# Note that unless we are in strict mode (which we set during installation)
|
||||
# we don't validate dependencies of locally installed gems but
|
||||
# accept what's in the lockfile instead for performance, since loading
|
||||
# dependencies of locally installed gems would mean evaluating all gemspecs,
|
||||
# which would affect `bundler/setup` performance.
|
||||
def validate_dependencies(spec)
|
||||
if !@materialization_options[:strict] && spec.is_a?(StubSpecification)
|
||||
spec.dependencies = dependencies
|
||||
else
|
||||
if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort
|
||||
raise IncorrectLockfileDependencies.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,158 @@
|
||||
# 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_checksums
|
||||
add_locked_ruby_version
|
||||
add_bundled_with
|
||||
|
||||
out
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_sources
|
||||
definition.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"
|
||||
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 << "\n"
|
||||
handled << dep.name
|
||||
end
|
||||
end
|
||||
|
||||
def add_checksums
|
||||
return unless definition.locked_checksums
|
||||
checksums = definition.resolve.map do |spec|
|
||||
spec.source.checksum_store.to_lock(spec)
|
||||
end
|
||||
|
||||
add_section("CHECKSUMS", checksums + bundler_checksum)
|
||||
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.bundler_version_to_lock.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
|
||||
|
||||
def bundler_checksum
|
||||
# In frozen mode the lockfile can't change, so reproduce whatever bundler
|
||||
# entry is already locked instead of recording one for the running bundler
|
||||
# version, which may legitimately differ from the locked one.
|
||||
return locked_bundler_checksum if Bundler.frozen_bundle?
|
||||
|
||||
# `.dev` versions and `SKIP_BUNDLER_CHECKSUM` are deliberate opt-outs (used
|
||||
# by Bundler/RubyGems' own development and release tasks): never record a
|
||||
# checksum for Bundler itself in those cases.
|
||||
return [] if Bundler.gem_version.to_s.end_with?(".dev") || ENV["SKIP_BUNDLER_CHECKSUM"]
|
||||
|
||||
bundler_spec = definition.sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last
|
||||
|
||||
# Record a fresh checksum from the locally cached gem when it's available.
|
||||
# When it isn't (e.g. a fresh checkout/CI that never downloaded the bundler
|
||||
# gem), fall back to whatever checksum is already locked rather than
|
||||
# dropping it, so the entry stays consistent across environments.
|
||||
if File.exist?(bundler_spec.cache_file)
|
||||
require "rubygems/package"
|
||||
|
||||
package = Gem::Package.new(bundler_spec.cache_file)
|
||||
definition.sources.metadata_source.checksum_store.register(bundler_spec, Checksum.from_gem_package(package))
|
||||
elsif bundled_with_changing?
|
||||
# We can't compute a fresh checksum (the bundler gem isn't cached) and the
|
||||
# BUNDLED WITH version is changing. Keeping the previously locked checksum
|
||||
# would leave a `bundler (<old version>) sha256=...` entry that no longer
|
||||
# matches the new BUNDLED WITH version, so drop it instead.
|
||||
return []
|
||||
end
|
||||
|
||||
return [] if definition.sources.metadata_source.checksum_store.missing?(bundler_spec)
|
||||
|
||||
[definition.sources.metadata_source.checksum_store.to_lock(bundler_spec)]
|
||||
end
|
||||
|
||||
def bundled_with_changing?
|
||||
locked_gems = definition.locked_gems
|
||||
return false unless locked_gems
|
||||
|
||||
locked_gems.bundler_version != definition.bundler_version_to_lock
|
||||
end
|
||||
|
||||
def locked_bundler_checksum
|
||||
locked_version = definition.locked_gems&.bundler_version
|
||||
return [] unless locked_version
|
||||
|
||||
metadata_source = definition.sources.metadata_source
|
||||
locked_spec = LazySpecification.new("bundler", locked_version, Gem::Platform::RUBY, metadata_source)
|
||||
return [] if metadata_source.checksum_store.missing?(locked_spec)
|
||||
|
||||
[metadata_source.checksum_store.to_lock(locked_spec)]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,328 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "shared_helpers"
|
||||
|
||||
module Bundler
|
||||
class LockfileParser
|
||||
class Position
|
||||
attr_reader :line, :column
|
||||
def initialize(line, column)
|
||||
@line = line
|
||||
@column = column
|
||||
end
|
||||
|
||||
def advance!(string)
|
||||
lines = string.count("\n")
|
||||
if lines > 0
|
||||
@line += lines
|
||||
@column = string.length - string.rindex("\n")
|
||||
else
|
||||
@column += string.length
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{line}:#{column}"
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader(
|
||||
:sources,
|
||||
:metadata_source,
|
||||
:dependencies,
|
||||
:specs,
|
||||
:platforms,
|
||||
:most_specific_locked_platform,
|
||||
:bundler_version,
|
||||
:ruby_version,
|
||||
:checksums,
|
||||
)
|
||||
|
||||
BUNDLED = "BUNDLED WITH"
|
||||
DEPENDENCIES = "DEPENDENCIES"
|
||||
CHECKSUMS = "CHECKSUMS"
|
||||
PLATFORMS = "PLATFORMS"
|
||||
RUBY = "RUBY VERSION"
|
||||
GIT = "GIT"
|
||||
GEM = "GEM"
|
||||
PATH = "PATH"
|
||||
PLUGIN = "PLUGIN SOURCE"
|
||||
SPECS = " specs:"
|
||||
OPTIONS = /^ ([a-z]+): (.*)$/i
|
||||
SOURCE = [GIT, GEM, PATH, PLUGIN].freeze
|
||||
|
||||
SECTIONS_BY_VERSION_INTRODUCED = {
|
||||
Gem::Version.create("1.0") => [DEPENDENCIES, PLATFORMS, GIT, GEM, PATH].freeze,
|
||||
Gem::Version.create("1.10") => [BUNDLED].freeze,
|
||||
Gem::Version.create("1.12") => [RUBY].freeze,
|
||||
Gem::Version.create("1.13") => [PLUGIN].freeze,
|
||||
Gem::Version.create("2.5.0") => [CHECKSUMS].freeze,
|
||||
}.freeze
|
||||
|
||||
KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten!.freeze
|
||||
|
||||
ENVIRONMENT_VERSION_SECTIONS = [BUNDLED, RUBY].freeze
|
||||
deprecate_constant(:ENVIRONMENT_VERSION_SECTIONS)
|
||||
|
||||
def self.sections_in_lockfile(lockfile_contents)
|
||||
sections = lockfile_contents.scan(/^\w[\w ]*$/)
|
||||
sections.uniq!
|
||||
sections
|
||||
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")
|
||||
attributes = []
|
||||
SECTIONS_BY_VERSION_INTRODUCED.each do |version, introduced|
|
||||
next if version <= base_version
|
||||
attributes += introduced
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
def self.bundled_with
|
||||
lockfile = Bundler.default_lockfile
|
||||
return unless lockfile.file?
|
||||
|
||||
lockfile_contents = Bundler.read_file(lockfile)
|
||||
return unless lockfile_contents.include?(BUNDLED)
|
||||
|
||||
lockfile_contents.split(BUNDLED).last.strip
|
||||
end
|
||||
|
||||
def initialize(lockfile, strict: false)
|
||||
@platforms = []
|
||||
@sources = []
|
||||
@metadata_source = Source::Metadata.new
|
||||
@dependencies = {}
|
||||
@parse_method = nil
|
||||
@specs = {}
|
||||
@lockfile_path = begin
|
||||
SharedHelpers.relative_lockfile_path
|
||||
rescue GemfileNotFound
|
||||
"Gemfile.lock"
|
||||
end
|
||||
@pos = Position.new(1, 1)
|
||||
@strict = strict
|
||||
|
||||
if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
|
||||
raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \
|
||||
"Run `git checkout HEAD -- #{@lockfile_path}` first to get a clean lock."
|
||||
end
|
||||
|
||||
@valid = lockfile.strip.empty? ||
|
||||
lockfile.split(/(?:\r?\n)+/).any? {|l| KNOWN_SECTIONS.include?(l) }
|
||||
|
||||
unless @valid
|
||||
SharedHelpers.feature_deprecated!(
|
||||
"Your #{@lockfile_path} does not appear to be a valid lockfile. " \
|
||||
"Run `rm #{@lockfile_path}` and then `bundle install` to generate a new lockfile. " \
|
||||
"This will raise a LockfileError in a future version of Bundler."
|
||||
)
|
||||
end
|
||||
|
||||
lockfile.split(/((?:\r?\n)+)/) do |line|
|
||||
# split alternates between the line and the following whitespace
|
||||
next @pos.advance!(line) if line.match?(/^\s*$/)
|
||||
|
||||
if SOURCE.include?(line)
|
||||
@parse_method = :parse_source
|
||||
parse_source(line)
|
||||
elsif line == DEPENDENCIES
|
||||
@parse_method = :parse_dependency
|
||||
elsif line == CHECKSUMS
|
||||
# This is a temporary solution to make this feature disabled by default
|
||||
# for all gemfiles that don't already explicitly include the feature.
|
||||
@checksums = true
|
||||
@parse_method = :parse_checksum
|
||||
elsif line == PLATFORMS
|
||||
@parse_method = :parse_platform
|
||||
elsif line == RUBY
|
||||
@parse_method = :parse_ruby
|
||||
elsif line == BUNDLED
|
||||
@parse_method = :parse_bundled_with
|
||||
elsif /^[^\s]/.match?(line)
|
||||
@parse_method = nil
|
||||
elsif @parse_method
|
||||
send(@parse_method, line)
|
||||
end
|
||||
@pos.advance!(line)
|
||||
end
|
||||
|
||||
if @platforms.include?(Gem::Platform::X64_MINGW_LEGACY)
|
||||
SharedHelpers.feature_deprecated!("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.")
|
||||
end
|
||||
|
||||
@most_specific_locked_platform = @platforms.min_by do |bundle_platform|
|
||||
Gem::Platform.platform_specificity_match(bundle_platform, Bundler.local_platform)
|
||||
end
|
||||
@specs = @specs.values.sort_by!(&:full_name).each do |spec|
|
||||
spec.most_specific_locked_platform = @most_specific_locked_platform
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
Bundler.ui.debug(e)
|
||||
raise LockfileError, "Your lockfile is unreadable. Run `rm #{@lockfile_path}` " \
|
||||
"and then `bundle install` to generate a new lockfile. The error occurred while " \
|
||||
"evaluating #{@lockfile_path}:#{@pos}"
|
||||
end
|
||||
|
||||
def may_include_redundant_platform_specific_gems?
|
||||
bundler_version.nil? || bundler_version < Gem::Version.new("1.16.2")
|
||||
end
|
||||
|
||||
def valid?
|
||||
@valid
|
||||
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
|
||||
return unless TYPES.key?(@type)
|
||||
@current_source = TYPES[@type].from_lock(@opts)
|
||||
@sources << @current_source
|
||||
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
|
||||
(?:#{space}([^ ]+))? # Optional checksum
|
||||
$ # 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(",").each(&: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_checksum(line)
|
||||
return unless line =~ NAME_VERSION
|
||||
|
||||
spaces = $1
|
||||
return unless spaces.size == 2
|
||||
checksums = $6
|
||||
name = $2
|
||||
version = $3
|
||||
platform = $4
|
||||
|
||||
version = Gem::Version.new(version)
|
||||
platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
|
||||
full_name = Gem::NameTuple.new(name, version, platform).full_name
|
||||
spec = @specs[full_name]
|
||||
|
||||
if name == "bundler"
|
||||
spec ||= LazySpecification.new(name, version, platform, @metadata_source)
|
||||
end
|
||||
return unless spec
|
||||
|
||||
if checksums
|
||||
checksums.split(",") do |lock_checksum|
|
||||
column = line.index(lock_checksum) + 1
|
||||
checksum = Checksum.from_lock(lock_checksum, "#{@lockfile_path}:#{@pos.line}:#{column}")
|
||||
spec.source.checksum_store.register(spec, checksum)
|
||||
end
|
||||
else
|
||||
spec.source.checksum_store.register(spec, nil)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_spec(line)
|
||||
return unless line =~ NAME_VERSION
|
||||
spaces = $1
|
||||
name = -$2
|
||||
version = $3
|
||||
|
||||
if spaces.size == 4
|
||||
# only load platform for non-dependency (spec) line
|
||||
platform = $4
|
||||
|
||||
version = Gem::Version.new(version)
|
||||
platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
|
||||
@current_spec = LazySpecification.new(name, version, platform, @current_source, strict: @strict)
|
||||
@current_source.add_dependency_names(name)
|
||||
|
||||
@specs[@current_spec.full_name] = @current_spec
|
||||
elsif spaces.size == 6
|
||||
version = version.split(",").each(&:strip!) if version
|
||||
dep = Gem::Dependency.new(name, version)
|
||||
@current_spec.dependencies << dep
|
||||
end
|
||||
end
|
||||
|
||||
def parse_platform(line)
|
||||
@platforms << Gem::Platform.new($1.strip) if line =~ /^ (.*)$/
|
||||
end
|
||||
|
||||
def parse_bundled_with(line)
|
||||
line.strip!
|
||||
return unless Gem::Version.correct?(line)
|
||||
@bundler_version = Gem::Version.create(line)
|
||||
end
|
||||
|
||||
def parse_ruby(line)
|
||||
line.strip!
|
||||
@ruby_version = line
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
# Ignore all files in this directory
|
||||
@@ -0,0 +1,79 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-ADD" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-path=PATH] [\-\-git=GIT|\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-cooldown=NUMBER] [\-\-quiet] [\-\-skip\-install] [\-\-strict|\-\-optimistic]
|
||||
.SH "DESCRIPTION"
|
||||
Adds the named gem to the [\fBGemfile(5)\fR][Gemfile(5)] and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-\-version=VERSION\fR, \fB\-v=VERSION\fR
|
||||
Specify version requirements(s) for the added gem\.
|
||||
.TP
|
||||
\fB\-\-group=GROUP\fR, \fB\-g=GROUP\fR
|
||||
Specify the group(s) for the added gem\. Multiple groups should be separated by commas\.
|
||||
.TP
|
||||
\fB\-\-source=SOURCE\fR, \fB\-s=SOURCE\fR
|
||||
Specify the source for the added gem\.
|
||||
.TP
|
||||
\fB\-\-require=REQUIRE\fR, \fB\-r=REQUIRE\fR
|
||||
Adds require path to gem\. Provide false, or a path as a string\.
|
||||
.TP
|
||||
\fB\-\-path=PATH\fR
|
||||
Specify the file system path for the added gem\.
|
||||
.TP
|
||||
\fB\-\-git=GIT\fR
|
||||
Specify the git source for the added gem\.
|
||||
.TP
|
||||
\fB\-\-github=GITHUB\fR
|
||||
Specify the github source for the added gem\.
|
||||
.TP
|
||||
\fB\-\-branch=BRANCH\fR
|
||||
Specify the git branch for the added gem\.
|
||||
.TP
|
||||
\fB\-\-ref=REF\fR
|
||||
Specify the git ref for the added gem\.
|
||||
.TP
|
||||
\fB\-\-glob=GLOB\fR
|
||||
Specify the location of a dependency's \.gemspec, expanded within Ruby (single quotes recommended)\.
|
||||
.TP
|
||||
\fB\-\-quiet\fR
|
||||
Do not print progress information to the standard output\.
|
||||
.TP
|
||||
\fB\-\-skip\-install\fR
|
||||
Adds the gem to the Gemfile but does not install it\.
|
||||
.TP
|
||||
\fB\-\-optimistic\fR
|
||||
Adds optimistic declaration of version\.
|
||||
.TP
|
||||
\fB\-\-strict\fR
|
||||
Adds strict declaration of version\.
|
||||
.TP
|
||||
\fB\-\-cooldown=<number>\fR
|
||||
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run\. See \fBcooldown\fR in bundle\-config(1) for precedence rules\.
|
||||
.SH "EXAMPLES"
|
||||
.IP "1." 4
|
||||
You can add the \fBrails\fR gem to the Gemfile without any version restriction\. The source of the gem will be the global source\.
|
||||
.IP
|
||||
\fBbundle add rails\fR
|
||||
.IP "2." 4
|
||||
You can add the \fBrails\fR gem with version greater than 1\.1 (not including 1\.1) and less than 3\.0\.
|
||||
.IP
|
||||
\fBbundle add rails \-\-version "> 1\.1, < 3\.0"\fR
|
||||
.IP "3." 4
|
||||
You can use the \fBhttps://gems\.example\.com\fR custom source and assign the gem to a group\.
|
||||
.IP
|
||||
\fBbundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"\fR
|
||||
.IP "4." 4
|
||||
The following adds the \fBgem\fR entry to the Gemfile without installing the gem\. You can install gems later via \fBbundle install\fR\.
|
||||
.IP
|
||||
\fBbundle add rails \-\-skip\-install\fR
|
||||
.IP "5." 4
|
||||
You can assign the gem to more than one group\.
|
||||
.IP
|
||||
\fBbundle add rails \-\-group "development, test"\fR
|
||||
.IP "" 0
|
||||
.SH "SEE ALSO"
|
||||
Gemfile(5) \fIhttps://guides\.rubygems\.org/gemfile/\fR, bundle\-remove(1) \fIbundle\-remove\.1\.html\fR
|
||||
@@ -0,0 +1,92 @@
|
||||
bundle-add(1) -- Add gem to the Gemfile and run bundle install
|
||||
==============================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle add` <GEM_NAME> [--group=GROUP] [--version=VERSION] [--source=SOURCE]
|
||||
[--path=PATH] [--git=GIT|--github=GITHUB] [--branch=BRANCH] [--ref=REF]
|
||||
[--cooldown=NUMBER] [--quiet] [--skip-install] [--strict|--optimistic]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.
|
||||
`bundle install` can be avoided by using the flag `--skip-install`.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `--version=VERSION`, `-v=VERSION`:
|
||||
Specify version requirements(s) for the added gem.
|
||||
|
||||
* `--group=GROUP`, `-g=GROUP`:
|
||||
Specify the group(s) for the added gem. Multiple groups should be separated by commas.
|
||||
|
||||
* `--source=SOURCE`, `-s=SOURCE`:
|
||||
Specify the source for the added gem.
|
||||
|
||||
* `--require=REQUIRE`, `-r=REQUIRE`:
|
||||
Adds require path to gem. Provide false, or a path as a string.
|
||||
|
||||
* `--path=PATH`:
|
||||
Specify the file system path for the added gem.
|
||||
|
||||
* `--git=GIT`:
|
||||
Specify the git source for the added gem.
|
||||
|
||||
* `--github=GITHUB`:
|
||||
Specify the github source for the added gem.
|
||||
|
||||
* `--branch=BRANCH`:
|
||||
Specify the git branch for the added gem.
|
||||
|
||||
* `--ref=REF`:
|
||||
Specify the git ref for the added gem.
|
||||
|
||||
* `--glob=GLOB`:
|
||||
Specify the location of a dependency's .gemspec, expanded within Ruby (single quotes recommended).
|
||||
|
||||
* `--quiet`:
|
||||
Do not print progress information to the standard output.
|
||||
|
||||
* `--skip-install`:
|
||||
Adds the gem to the Gemfile but does not install it.
|
||||
|
||||
* `--optimistic`:
|
||||
Adds optimistic declaration of version.
|
||||
|
||||
* `--strict`:
|
||||
Adds strict declaration of version.
|
||||
|
||||
* `--cooldown=<number>`:
|
||||
Only consider gem versions published at least <number> days ago when
|
||||
resolving. Pass `0` to disable cooldown for this run. See `cooldown`
|
||||
in bundle-config(1) for precedence rules.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
1. You can add the `rails` gem to the Gemfile without any version restriction.
|
||||
The source of the gem will be the global source.
|
||||
|
||||
`bundle add rails`
|
||||
|
||||
2. You can add the `rails` gem with version greater than 1.1 (not including 1.1) and less than 3.0.
|
||||
|
||||
`bundle add rails --version "> 1.1, < 3.0"`
|
||||
|
||||
3. You can use the `https://gems.example.com` custom source and assign the gem
|
||||
to a group.
|
||||
|
||||
`bundle add rails --version "~> 5.0.0" --source "https://gems.example.com" --group "development"`
|
||||
|
||||
4. The following adds the `gem` entry to the Gemfile without installing the
|
||||
gem. You can install gems later via `bundle install`.
|
||||
|
||||
`bundle add rails --skip-install`
|
||||
|
||||
5. You can assign the gem to more than one group.
|
||||
|
||||
`bundle add rails --group "development, test"`
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
[Gemfile(5)](https://guides.rubygems.org/gemfile/),
|
||||
[bundle-remove(1)](bundle-remove.1.html)
|
||||
@@ -0,0 +1,30 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-BINSTUBS" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-standalone] [\-\-all\-platforms]
|
||||
.SH "DESCRIPTION"
|
||||
Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it into \fBbin/\fR\. Binstubs are a shortcut\-or alternative\- to always using \fBbundle exec\fR\. This gives you a file that can be run directly, and one that will always run the correct gem version used by the application\.
|
||||
.P
|
||||
For example, if you run \fBbundle binstubs rspec\-core\fR, Bundler will create the file \fBbin/rspec\fR\. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec\.
|
||||
.P
|
||||
This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the directory specified by \fBbin\fR setting if it has been configured\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-\-force\fR
|
||||
Overwrite existing binstubs if they exist\.
|
||||
.TP
|
||||
\fB\-\-standalone\fR
|
||||
Makes binstubs that can work without depending on Rubygems or Bundler at runtime\.
|
||||
.TP
|
||||
\fB\-\-shebang=SHEBANG\fR
|
||||
Specify a different shebang executable name than the default (default 'ruby')
|
||||
.TP
|
||||
\fB\-\-all\fR
|
||||
Create binstubs for all gems in the bundle\.
|
||||
.TP
|
||||
\fB\-\-all\-platforms\fR
|
||||
Install binstubs for all platforms\.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
bundle-binstubs(1) -- Install the binstubs of the listed gems
|
||||
=============================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle binstubs` <GEM_NAME> [--force] [--standalone] [--all-platforms]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Binstubs are scripts that wrap around executables. Bundler creates a
|
||||
small Ruby file (a binstub) that loads Bundler, runs the command,
|
||||
and puts it into `bin/`. Binstubs are a shortcut-or alternative-
|
||||
to always using `bundle exec`. This gives you a file that can be run
|
||||
directly, and one that will always run the correct gem version
|
||||
used by the application.
|
||||
|
||||
For example, if you run `bundle binstubs rspec-core`, Bundler will create
|
||||
the file `bin/rspec`. That file will contain enough code to load Bundler,
|
||||
tell it to load the bundled gems, and then run rspec.
|
||||
|
||||
This command generates binstubs for executables in `GEM_NAME`.
|
||||
Binstubs are put into `bin`, or the directory specified by `bin` setting if it
|
||||
has been configured. Calling binstubs with [GEM [GEM]] will create binstubs for
|
||||
all given gems.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `--force`:
|
||||
Overwrite existing binstubs if they exist.
|
||||
|
||||
* `--standalone`:
|
||||
Makes binstubs that can work without depending on Rubygems or Bundler at
|
||||
runtime.
|
||||
|
||||
* `--shebang=SHEBANG`:
|
||||
Specify a different shebang executable name than the default (default 'ruby')
|
||||
|
||||
* `--all`:
|
||||
Create binstubs for all gems in the bundle.
|
||||
|
||||
* `--all-platforms`:
|
||||
Install binstubs for all platforms.
|
||||
@@ -0,0 +1,59 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-CACHE" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle cache\fR [\fIOPTIONS\fR]
|
||||
.P
|
||||
alias: \fBpackage\fR, \fBpack\fR
|
||||
.SH "DESCRIPTION"
|
||||
Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR, use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-\-all\-platforms\fR
|
||||
Include gems for all platforms present in the lockfile, not only the current one\.
|
||||
.TP
|
||||
\fB\-\-cache\-path=CACHE\-PATH\fR
|
||||
Specify a different cache path than the default (vendor/cache)\.
|
||||
.TP
|
||||
\fB\-\-cooldown=<number>\fR
|
||||
Only consider gem versions published at least \fInumber\fR days ago when resolving before caching\. Pass \fB0\fR to disable cooldown for this run, overriding any per\-source or global configuration\. See \fBcooldown\fR in bundle\-config(1)\.
|
||||
.TP
|
||||
\fB\-\-gemfile=GEMFILE\fR
|
||||
Use the specified gemfile instead of Gemfile\.
|
||||
.TP
|
||||
\fB\-\-no\-install\fR
|
||||
Don't install the gems, only update the cache\.
|
||||
.TP
|
||||
\fB\-\-quiet\fR
|
||||
Only output warnings and errors\.
|
||||
.SH "GIT AND PATH GEMS"
|
||||
The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This can be disabled setting \fBcache_all\fR to false\.
|
||||
.SH "SUPPORT FOR MULTIPLE PLATFORMS"
|
||||
When using gems that have different packages for different platforms, Bundler supports caching of gems for other platforms where the Gemfile has been resolved (i\.e\. present in the lockfile) in \fBvendor/cache\fR\. This needs to be enabled via the \fB\-\-all\-platforms\fR option\. This setting will be remembered in your local bundler configuration\.
|
||||
.SH "REMOTE FETCHING"
|
||||
By default, if you run \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR after running bundle cache(1) \fIbundle\-cache\.1\.html\fR, bundler will still connect to \fBrubygems\.org\fR to check whether a platform\-specific gem exists for any of the gems in \fBvendor/cache\fR\.
|
||||
.P
|
||||
For instance, consider this Gemfile(5):
|
||||
.IP "" 4
|
||||
.nf
|
||||
source "https://rubygems\.org"
|
||||
|
||||
gem "nokogiri"
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
If you run \fBbundle cache\fR under C Ruby, bundler will retrieve the version of \fBnokogiri\fR for the \fB"ruby"\fR platform\. If you deploy to JRuby and run \fBbundle install\fR, bundler is forced to check to see whether a \fB"java"\fR platformed \fBnokogiri\fR exists\.
|
||||
.P
|
||||
Even though the \fBnokogiri\fR gem for the Ruby platform is \fItechnically\fR acceptable on JRuby, it has a C extension that does not run on JRuby\. As a result, bundler will, by default, still connect to \fBrubygems\.org\fR to check whether it has a version of one of your gems more specific to your platform\.
|
||||
.P
|
||||
This problem is also not limited to the \fB"java"\fR platform\. A similar (common) problem can happen when developing on Windows and deploying to Linux, or even when developing on OSX and deploying to Linux\.
|
||||
.P
|
||||
If you know for sure that the gems packaged in \fBvendor/cache\fR are appropriate for the platform you are on, you can run \fBbundle install \-\-local\fR to skip checking for more appropriate gems, and use the ones in \fBvendor/cache\fR\.
|
||||
.P
|
||||
One way to be sure that you have the right platformed versions of all your gems is to run \fBbundle cache\fR on an identical machine and check in the gems\. For instance, you can run \fBbundle cache\fR on an identical staging box during your staging process, and check in the \fBvendor/cache\fR before deploying to production\.
|
||||
.P
|
||||
By default, bundle cache(1) \fIbundle\-cache\.1\.html\fR fetches and also installs the gems to the default location\. To package the dependencies to \fBvendor/cache\fR without installing them to the local install location, you can run \fBbundle cache \-\-no\-install\fR\.
|
||||
.SH "HISTORY"
|
||||
In Bundler 2\.1, \fBcache\fR took in the functionalities of \fBpackage\fR and now \fBpackage\fR and \fBpack\fR are aliases of \fBcache\fR\.
|
||||
@@ -0,0 +1,101 @@
|
||||
bundle-cache(1) -- Package your needed `.gem` files into your application
|
||||
=========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle cache` [*OPTIONS*]
|
||||
|
||||
alias: `package`, `pack`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Copy all of the `.gem` files needed to run the application into the
|
||||
`vendor/cache` directory. In the future, when running [`bundle install(1)`](bundle-install.1.html),
|
||||
use the gems in the cache in preference to the ones on `rubygems.org`.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `--all-platforms`:
|
||||
Include gems for all platforms present in the lockfile, not only the current one.
|
||||
|
||||
* `--cache-path=CACHE-PATH`:
|
||||
Specify a different cache path than the default (vendor/cache).
|
||||
|
||||
* `--cooldown=<number>`:
|
||||
Only consider gem versions published at least <number> days ago when
|
||||
resolving before caching. Pass `0` to disable cooldown for this run,
|
||||
overriding any per-source or global configuration. See `cooldown` in
|
||||
bundle-config(1).
|
||||
|
||||
* `--gemfile=GEMFILE`:
|
||||
Use the specified gemfile instead of Gemfile.
|
||||
|
||||
* `--no-install`:
|
||||
Don't install the gems, only update the cache.
|
||||
|
||||
* `--quiet`:
|
||||
Only output warnings and errors.
|
||||
|
||||
## GIT AND PATH GEMS
|
||||
|
||||
The `bundle cache` command can also package `:git` and `:path` dependencies
|
||||
besides .gem files. This can be disabled setting `cache_all` to false.
|
||||
|
||||
## SUPPORT FOR MULTIPLE PLATFORMS
|
||||
|
||||
When using gems that have different packages for different platforms, Bundler
|
||||
supports caching of gems for other platforms where the Gemfile has been resolved
|
||||
(i.e. present in the lockfile) in `vendor/cache`. This needs to be enabled via
|
||||
the `--all-platforms` option. This setting will be remembered in your local
|
||||
bundler configuration.
|
||||
|
||||
## REMOTE FETCHING
|
||||
|
||||
By default, if you run [`bundle install(1)`](bundle-install.1.html) after running
|
||||
[bundle cache(1)](bundle-cache.1.html), bundler will still connect to `rubygems.org`
|
||||
to check whether a platform-specific gem exists for any of the gems
|
||||
in `vendor/cache`.
|
||||
|
||||
For instance, consider this Gemfile(5):
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "nokogiri"
|
||||
|
||||
If you run `bundle cache` under C Ruby, bundler will retrieve
|
||||
the version of `nokogiri` for the `"ruby"` platform. If you deploy
|
||||
to JRuby and run `bundle install`, bundler is forced to check to
|
||||
see whether a `"java"` platformed `nokogiri` exists.
|
||||
|
||||
Even though the `nokogiri` gem for the Ruby platform is
|
||||
_technically_ acceptable on JRuby, it has a C extension
|
||||
that does not run on JRuby. As a result, bundler will, by default,
|
||||
still connect to `rubygems.org` to check whether it has a version
|
||||
of one of your gems more specific to your platform.
|
||||
|
||||
This problem is also not limited to the `"java"` platform.
|
||||
A similar (common) problem can happen when developing on Windows
|
||||
and deploying to Linux, or even when developing on OSX and
|
||||
deploying to Linux.
|
||||
|
||||
If you know for sure that the gems packaged in `vendor/cache`
|
||||
are appropriate for the platform you are on, you can run
|
||||
`bundle install --local` to skip checking for more appropriate
|
||||
gems, and use the ones in `vendor/cache`.
|
||||
|
||||
One way to be sure that you have the right platformed versions
|
||||
of all your gems is to run `bundle cache` on an identical
|
||||
machine and check in the gems. For instance, you can run
|
||||
`bundle cache` on an identical staging box during your
|
||||
staging process, and check in the `vendor/cache` before
|
||||
deploying to production.
|
||||
|
||||
By default, [bundle cache(1)](bundle-cache.1.html) fetches and also
|
||||
installs the gems to the default location. To package the
|
||||
dependencies to `vendor/cache` without installing them to the
|
||||
local install location, you can run `bundle cache --no-install`.
|
||||
|
||||
## HISTORY
|
||||
|
||||
In Bundler 2.1, `cache` took in the functionalities of `package` and now
|
||||
`package` and `pack` are aliases of `cache`.
|
||||
@@ -0,0 +1,21 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-CHECK" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE]
|
||||
.SH "DESCRIPTION"
|
||||
\fBcheck\fR 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\.
|
||||
.P
|
||||
If not, the first missing gem is listed and Bundler exits status 1\.
|
||||
.P
|
||||
If the lockfile needs to be updated then it will be resolved using the gems installed on the local machine, if they satisfy the requirements\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-\-dry\-run\fR
|
||||
Locks the [\fBGemfile(5)\fR][Gemfile(5)] before running the command\.
|
||||
.TP
|
||||
\fB\-\-gemfile=GEMFILE\fR
|
||||
Use the specified gemfile instead of the [\fBGemfile(5)\fR][Gemfile(5)]\.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
bundle-check(1) -- Verifies if dependencies are satisfied by installed gems
|
||||
===========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle check` [--dry-run]
|
||||
[--gemfile=FILE]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`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.
|
||||
|
||||
If the lockfile needs to be updated then it will be resolved using the gems
|
||||
installed on the local machine, if they satisfy the requirements.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `--dry-run`:
|
||||
Locks the [`Gemfile(5)`][Gemfile(5)] before running the command.
|
||||
|
||||
* `--gemfile=GEMFILE`:
|
||||
Use the specified gemfile instead of the [`Gemfile(5)`][Gemfile(5)].
|
||||
@@ -0,0 +1,17 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-CLEAN" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle clean\fR [\-\-dry\-run] [\-\-force]
|
||||
.SH "DESCRIPTION"
|
||||
This command will remove all unused gems in your bundler directory\. This is useful when you have made many changes to your gem dependencies\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-\-dry\-run\fR
|
||||
Print the changes, but do not clean the unused gems\.
|
||||
.TP
|
||||
\fB\-\-force\fR
|
||||
Forces cleaning up unused gems even if Bundler is configured to use globally installed gems\. As a consequence, removes all system gems except for the ones in the current application\.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
bundle-clean(1) -- Cleans up unused gems in your bundler directory
|
||||
==================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle clean` [--dry-run] [--force]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
This command will remove all unused gems in your bundler directory. This is
|
||||
useful when you have made many changes to your gem dependencies.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `--dry-run`:
|
||||
Print the changes, but do not clean the unused gems.
|
||||
* `--force`:
|
||||
Forces cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application.
|
||||
@@ -0,0 +1,339 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-CONFIG" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-config\fR \- Set bundler configuration options
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle config\fR [list]
|
||||
.br
|
||||
\fBbundle config\fR [get [\-\-local|\-\-global]] NAME
|
||||
.br
|
||||
\fBbundle config\fR [set [\-\-local|\-\-global]] NAME VALUE
|
||||
.br
|
||||
\fBbundle config\fR unset [\-\-local|\-\-global] NAME
|
||||
.SH "DESCRIPTION"
|
||||
This command allows you to interact with Bundler's configuration system\.
|
||||
.P
|
||||
Bundler loads configuration settings in this order:
|
||||
.IP "1." 4
|
||||
Local config (\fB<project_root>/\.bundle/config\fR or \fB$BUNDLE_APP_CONFIG/config\fR)
|
||||
.IP "2." 4
|
||||
Environmental variables (\fBENV\fR)
|
||||
.IP "3." 4
|
||||
Global config (\fB~/\.bundle/config\fR)
|
||||
.IP "4." 4
|
||||
Bundler default config
|
||||
.IP "" 0
|
||||
.P
|
||||
Executing \fBbundle\fR with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
|
||||
.SH "SUB\-COMMANDS"
|
||||
.SS "list (default command)"
|
||||
Executing \fBbundle config list\fR will print a list of all bundler configuration for the current bundle, and where that configuration was set\.
|
||||
.SS "get"
|
||||
Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and all locations where it was set\.
|
||||
.P
|
||||
\fBOPTIONS\fR
|
||||
.TP
|
||||
\fB\-\-local\fR
|
||||
Get configuration from configuration file for the local application, namely, \fB<project_root>/\.bundle/config\fR, or \fB$BUNDLE_APP_CONFIG/config\fR if \fBBUNDLE_APP_CONFIG\fR is set\.
|
||||
.TP
|
||||
\fB\-\-global\fR
|
||||
Get configuration from configuration file global to all bundles executed as the current user, namely, from \fB~/\.bundle/config\fR\.
|
||||
.SS "set"
|
||||
Executing \fBbundle config set <name> <value>\fR defaults to setting \fBlocal\fR configuration if executing from within a local application, otherwise it will set \fBglobal\fR configuration\.
|
||||
.P
|
||||
\fBOPTIONS\fR
|
||||
.TP
|
||||
\fB\-\-local\fR
|
||||
Executing \fBbundle config set \-\-local <name> <value>\fR will set that configuration in the directory for the local application\. The configuration will be stored in \fB<project_root>/\.bundle/config\fR\. If \fBBUNDLE_APP_CONFIG\fR is set, the configuration will be stored in \fB$BUNDLE_APP_CONFIG/config\fR\.
|
||||
.TP
|
||||
\fB\-\-global\fR
|
||||
Executing \fBbundle config set \-\-global <name> <value>\fR will set that configuration to the value specified for all bundles executed as the current user\. The configuration will be stored in \fB~/\.bundle/config\fR\. If \fIname\fR already is set, \fIname\fR will be overridden and user will be warned\.
|
||||
.SS "unset"
|
||||
Executing \fBbundle config unset <name>\fR will delete the configuration in both local and global sources\.
|
||||
.P
|
||||
\fBOPTIONS\fR
|
||||
.TP
|
||||
\fB\-\-local\fR
|
||||
Executing \fBbundle config unset \-\-local <name>\fR will delete the configuration only from the local application\.
|
||||
.TP
|
||||
\fB\-\-global\fR
|
||||
Executing \fBbundle config unset \-\-global <name>\fR will delete the configuration only from the user configuration\.
|
||||
.SH "CONFIGURATION KEYS"
|
||||
Configuration keys in bundler have two forms: the canonical form and the environment variable form\.
|
||||
.P
|
||||
For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\.
|
||||
.P
|
||||
The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\.
|
||||
.P
|
||||
Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\.
|
||||
.SH "LIST OF AVAILABLE KEYS"
|
||||
The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBapi_request_size\fR (\fBBUNDLE_API_REQUEST_SIZE\fR): Configure how many dependencies to fetch when resolving the specifications\. This configuration is only used when fetchig specifications from RubyGems servers that didn't implement the Compact Index API\. Defaults to 100\.
|
||||
.IP "\(bu" 4
|
||||
\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\.
|
||||
.IP "\(bu" 4
|
||||
\fBbin\fR (\fBBUNDLE_BIN\fR): If configured, \fBbundle binstubs\fR will install executables from gems in the bundle to the specified directory\. Otherwise it will create them in a \fBbin\fR directory relative to the Gemfile directory\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, \fBbundle binstubs\fR will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\.
|
||||
.IP "\(bu" 4
|
||||
\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly before bundler 4, but will be the default on bundler 4\.
|
||||
.IP "\(bu" 4
|
||||
\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\.
|
||||
.IP "\(bu" 4
|
||||
\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 5, as long as \fBpath\fR is not explicitly configured\.
|
||||
.IP "\(bu" 4
|
||||
\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBcooldown\fR (\fBBUNDLE_COOLDOWN\fR): Number of days a published gem version must age before bundler will resolve to it\. Defaults to unset (no cooldown)\. Pass \fB0\fR to disable cooldown for an individual run\.
|
||||
.IP
|
||||
The effective cooldown for any given gem is resolved from three layers, highest precedence first:
|
||||
.IP "1." 4
|
||||
CLI flag \fB\-\-cooldown N\fR on \fBinstall\fR, \fBupdate\fR, \fBadd\fR, and \fBoutdated\fR\.
|
||||
.IP "2." 4
|
||||
This setting (\fBbundle config set cooldown N\fR or \fBBUNDLE_COOLDOWN=N\fR)\.
|
||||
.IP "3." 4
|
||||
The per\-source \fBcooldown:\fR keyword in the Gemfile, such as \fBsource "https://rubygems\.org", cooldown: 7\fR\.
|
||||
.IP "" 0
|
||||
.IP
|
||||
The CLI flag and this setting apply uniformly to every source, including ones declared with their own \fBcooldown:\fR value\. To keep a private registry permanently exempt while still cooling down public gems, declare \fBsource "https://internal", cooldown: 0\fR in the Gemfile; remember that \fB\-\-cooldown N\fR on the command line will still override it for that single run\.
|
||||
.IP
|
||||
Cooldown filtering depends on the gem server providing a per\-version \fBcreated_at\fR timestamp in the v2 compact\-index format\. Versions without that metadata \- older gem servers, historical entries that predate the v2 cutover on \fBrubygems\.org\fR, or private registries that still emit the v1 format \- are treated as outside the cooldown window and remain resolvable\. If you rely on cooldown for supply\-chain protection, confirm that the gem server emits \fBcreated_at\fR in its \fB/info/<gem>\fR responses\.
|
||||
.IP "\(bu" 4
|
||||
\fBdefault_cli_command\fR (\fBBUNDLE_DEFAULT_CLI_COMMAND\fR): The command that running \fBbundle\fR without arguments should run\. Defaults to \fBcli_help\fR since Bundler 4, but can also be \fBinstall\fR which was the previous default\.
|
||||
.IP "\(bu" 4
|
||||
\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Equivalent to setting \fBfrozen\fR to \fBtrue\fR and \fBpath\fR to \fBvendor/bundle\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\.
|
||||
.IP "\(bu" 4
|
||||
\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\.
|
||||
.IP "\(bu" 4
|
||||
\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
|
||||
.IP "\(bu" 4
|
||||
\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems' normal location\.
|
||||
.IP "\(bu" 4
|
||||
\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
|
||||
.IP "\(bu" 4
|
||||
\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
|
||||
.IP "\(bu" 4
|
||||
\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow any automatic changes to \fBGemfile\.lock\fR\. Bundler commands will be blocked unless the lockfile can be installed exactly as written\. Usually this will happen when changing the \fBGemfile\fR manually and forgetting to update the lockfile through \fBbundle lock\fR or \fBbundle install\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in the \fBREADME\fR and \fB\.gemspec\fR files when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\.
|
||||
.IP "\(bu" 4
|
||||
\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems and compiled extensions globally, rather than locally to the configured installation path\.
|
||||
.IP "\(bu" 4
|
||||
\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR): When set, no funding requests will be printed\.
|
||||
.IP "\(bu" 4
|
||||
\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can download and install in parallel\. Defaults to the number of available processors\.
|
||||
.IP "\(bu" 4
|
||||
\fBlockfile\fR (\fBBUNDLE_LOCKFILE\fR): The path to the lockfile that bundler should use\. By default, Bundler adds \fB\.lock\fR to the end of the \fBgemfile\fR entry\. Can be set to \fBfalse\fR in the Gemfile to disable lockfile creation entirely (see gemfile(5))\.
|
||||
.IP "\(bu" 4
|
||||
\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR): Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\. Bundler's own checksum is only included when its \fB\.gem\fR file is cached, which may not be the case when Bundler is installed as a default gem\.
|
||||
.IP "\(bu" 4
|
||||
\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\.
|
||||
.IP "\(bu" 4
|
||||
\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\.
|
||||
.IP "\(bu" 4
|
||||
\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, because they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\.
|
||||
.IP "\(bu" 4
|
||||
\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. When not set, Bundler install by default to a \fB\.bundle\fR directory relative to repository root in Bundler 5, and to the default system path (\fBGem\.dir\fR) before Bundler 5\. That means that before Bundler 5, Bundler shares this location with Rubygems, and \fBgem install \|\.\|\.\|\.\fR will have gems installed in the same location and therefore, gems installed without \fBpath\fR set will show up by calling \fBgem list\fR\. This will not be the case in Bundler 5\.
|
||||
.IP "\(bu" 4
|
||||
\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
|
||||
.IP "\(bu" 4
|
||||
\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler's experimental plugin system\.
|
||||
.IP "\(bu" 4
|
||||
\fBprefer_patch\fR (\fBBUNDLE_PREFER_PATCH\fR): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\.
|
||||
.IP "\(bu" 4
|
||||
\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\.
|
||||
.IP "\(bu" 4
|
||||
\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\.
|
||||
.IP "\(bu" 4
|
||||
\fBsimulate_version\fR (\fBBUNDLE_SIMULATE_VERSION\fR): The virtual version Bundler should use for activating feature flags\. Can be used to simulate all the new functionality that will be enabled in a future major version\.
|
||||
.IP "\(bu" 4
|
||||
\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\.
|
||||
.IP "\(bu" 4
|
||||
\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\.
|
||||
.IP "\(bu" 4
|
||||
\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\.
|
||||
.IP "\(bu" 4
|
||||
\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
|
||||
.IP "\(bu" 4
|
||||
\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\.
|
||||
.IP "\(bu" 4
|
||||
\fBverbose\fR (\fBBUNDLE_VERBOSE\fR): Whether Bundler should print verbose output\. Defaults to \fBfalse\fR, unless the \fB\-\-verbose\fR CLI flag is used\.
|
||||
.IP "\(bu" 4
|
||||
\fBversion\fR (\fBBUNDLE_VERSION\fR): The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\.
|
||||
.IP "\(bu" 4
|
||||
\fBwith\fR (\fBBUNDLE_WITH\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\.
|
||||
.IP "\(bu" 4
|
||||
\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\.
|
||||
.IP "" 0
|
||||
.SH "BUILD OPTIONS"
|
||||
You can use \fBbundle config\fR to give Bundler the flags to pass to the gem installer every time bundler tries to install a particular gem\.
|
||||
.P
|
||||
A very common example, the \fBmysql\fR gem, requires Snow Leopard users to pass configuration flags to \fBgem install\fR to specify where to find the \fBmysql_config\fR executable\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
gem install mysql \-\- \-\-with\-mysql\-config=/usr/local/mysql/bin/mysql_config
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Since the specific location of that executable can change from machine to machine, you can specify these flags on a per\-machine basis\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-global build\.mysql \-\-with\-mysql\-config=/usr/local/mysql/bin/mysql_config
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
After running this command, every time bundler needs to install the \fBmysql\fR gem, it will pass along the flags you specified\.
|
||||
.SH "LOCAL GIT REPOS"
|
||||
Bundler also allows you to work against a git repository locally instead of using the remote version\. This can be achieved by setting up a local override:
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-local local\.GEM_NAME /path/to/local/git/repository
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Important: This feature only works for gems that are specified with a git source in your Gemfile\. It does not work for gems installed from RubyGems or other sources\. The gem must be defined with \fBgit:\fR option pointing to a remote repository\.
|
||||
.P
|
||||
For example, if your Gemfile contains:
|
||||
.IP "" 4
|
||||
.nf
|
||||
gem "rack", git: "https://github\.com/rack/rack\.git", branch: "main"
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Then you can use a local Rack repository by running:
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-local local\.rack ~/Work/git/rack
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Now instead of checking out the remote git repository, the local override will be used\. Similar to a path source, every time the local git repository change, changes will be automatically picked up by Bundler\. This means a commit in the local git repo will update the revision in the \fBGemfile\.lock\fR to the local git repo revision\. This requires the same attention as git submodules\. Before pushing to the remote, you need to ensure the local override was pushed, otherwise you may point to a commit that only exists in your local machine\. You'll also need to CGI escape your usernames and passwords as well\.
|
||||
.P
|
||||
Bundler does many checks to ensure a developer won't work with invalid references\. Particularly, we force a developer to specify a branch in the \fBGemfile\fR in order to use this feature\. If the branch specified in the \fBGemfile\fR and the current branch in the local git repository do not match, Bundler will abort\. This ensures that a developer is always working against the correct branches, and prevents accidental locking to a different branch\.
|
||||
.P
|
||||
Finally, Bundler also ensures that the current revision in the \fBGemfile\.lock\fR exists in the local git repository\. By doing this, Bundler forces you to fetch the latest changes in the remotes\.
|
||||
.P
|
||||
If you need to temporarily use a local version of a gem that is normally installed from RubyGems (not from git), use a path source instead:
|
||||
.IP "" 4
|
||||
.nf
|
||||
gem "rack", path: "~/Work/git/rack"
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SH "MIRRORS OF GEM SOURCES"
|
||||
Bundler supports overriding gem sources with mirrors\. This allows you to configure rubygems\.org as the gem source in your Gemfile while still using your mirror to fetch gems\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-global mirror\.SOURCE_URL MIRROR_URL
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
For example, to use a mirror of https://rubygems\.org hosted at https://example\.org:
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-global mirror\.https://rubygems\.org https://example\.org
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Each mirror also provides a fallback timeout setting\. If the mirror does not respond within the fallback timeout, Bundler will try to use the original server instead of the mirror\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-global mirror\.SOURCE_URL\.fallback_timeout TIMEOUT
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
For example, to fall back to rubygems\.org after 3 seconds:
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-global mirror\.https://rubygems\.org\.fallback_timeout 3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
The default fallback timeout is 0\.1 seconds, but the setting can currently only accept whole seconds (for example, 1, 15, or 30)\.
|
||||
.SH "CREDENTIALS FOR GEM SOURCES"
|
||||
Bundler allows you to configure credentials for any gem source, which allows you to avoid putting secrets into your Gemfile\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-global SOURCE_HOSTNAME USERNAME:PASSWORD
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
For example, to save the credentials of user \fBclaudette\fR for the gem source at \fBgems\.longerous\.com\fR, you would run:
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-global gems\.longerous\.com claudette:s00pers3krit
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Or you can set the credentials as an environment variable like this:
|
||||
.IP "" 4
|
||||
.nf
|
||||
export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit"
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
For gems with a git source with HTTP(S) URL you can specify credentials like so:
|
||||
.IP "" 4
|
||||
.nf
|
||||
bundle config set \-\-global https://github\.com/ruby/rubygems\.git username:password
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Or you can set the credentials as an environment variable like so:
|
||||
.IP "" 4
|
||||
.nf
|
||||
export BUNDLE_GITHUB__COM=username:password
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
This is especially useful for private repositories on hosts such as GitHub, where you can use personal OAuth tokens:
|
||||
.IP "" 4
|
||||
.nf
|
||||
export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x\-oauth\-basic
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Note that any configured credentials will be redacted by informative commands such as \fBbundle config list\fR or \fBbundle config get\fR, unless you use the \fB\-\-parseable\fR flag\. This is to avoid unintentionally leaking credentials when copy\-pasting bundler output\.
|
||||
.P
|
||||
Also note that to guarantee a sane mapping between valid environment variable names and valid host names, bundler makes the following transformations:
|
||||
.IP "\(bu" 4
|
||||
Any \fB\-\fR characters in a host name are mapped to a triple underscore (\fB___\fR) in the corresponding environment variable\.
|
||||
.IP "\(bu" 4
|
||||
Any \fB\.\fR characters in a host name are mapped to a double underscore (\fB__\fR) in the corresponding environment variable\.
|
||||
.IP "" 0
|
||||
.P
|
||||
This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you'll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\.
|
||||
.SH "CONFIGURE BUNDLER DIRECTORIES"
|
||||
Bundler's home, cache and plugin directories and config file can be configured through environment variables\. The default location for Bundler's home directory is \fB~/\.bundle\fR, which all directories inherit from by default\. The following outlines the available environment variables and their default values
|
||||
.IP "" 4
|
||||
.nf
|
||||
BUNDLE_USER_HOME : $HOME/\.bundle
|
||||
BUNDLE_USER_CACHE : $BUNDLE_USER_HOME/cache
|
||||
BUNDLE_USER_CONFIG : $BUNDLE_USER_HOME/config
|
||||
BUNDLE_USER_PLUGIN : $BUNDLE_USER_HOME/plugin
|
||||
.fi
|
||||
.IP "" 0
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
bundle-config(1) -- Set bundler configuration options
|
||||
=====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle config` [list]<br>
|
||||
`bundle config` [get [--local|--global]] NAME<br>
|
||||
`bundle config` [set [--local|--global]] NAME VALUE<br>
|
||||
`bundle config` unset [--local|--global] NAME
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
This command allows you to interact with Bundler's configuration system.
|
||||
|
||||
Bundler loads configuration settings in this order:
|
||||
|
||||
1. Local config (`<project_root>/.bundle/config` or `$BUNDLE_APP_CONFIG/config`)
|
||||
2. Environmental variables (`ENV`)
|
||||
3. Global config (`~/.bundle/config`)
|
||||
4. Bundler default config
|
||||
|
||||
Executing `bundle` with the `BUNDLE_IGNORE_CONFIG` environment variable set will
|
||||
cause it to ignore all configuration.
|
||||
|
||||
## SUB-COMMANDS
|
||||
|
||||
### list (default command)
|
||||
|
||||
Executing `bundle config list` will print a list of all bundler
|
||||
configuration for the current bundle, and where that configuration
|
||||
was set.
|
||||
|
||||
### get
|
||||
|
||||
Executing `bundle config get <name>` will print the value of that configuration
|
||||
setting, and all locations where it was set.
|
||||
|
||||
**OPTIONS**
|
||||
|
||||
* `--local`:
|
||||
Get configuration from configuration file for the local application, namely,
|
||||
`<project_root>/.bundle/config`, or `$BUNDLE_APP_CONFIG/config` if
|
||||
`BUNDLE_APP_CONFIG` is set.
|
||||
|
||||
* `--global`:
|
||||
Get configuration from configuration file global to all bundles executed as
|
||||
the current user, namely, from `~/.bundle/config`.
|
||||
|
||||
### set
|
||||
|
||||
Executing `bundle config set <name> <value>` defaults to setting `local`
|
||||
configuration if executing from within a local application, otherwise it will
|
||||
set `global` configuration.
|
||||
|
||||
**OPTIONS**
|
||||
|
||||
* `--local`:
|
||||
Executing `bundle config set --local <name> <value>` will set that configuration
|
||||
in the directory for the local application. The configuration will be stored in
|
||||
`<project_root>/.bundle/config`. If `BUNDLE_APP_CONFIG` is set, the configuration
|
||||
will be stored in `$BUNDLE_APP_CONFIG/config`.
|
||||
|
||||
* `--global`:
|
||||
Executing `bundle config set --global <name> <value>` will set that
|
||||
configuration to the value specified for all bundles executed as the current
|
||||
user. The configuration will be stored in `~/.bundle/config`. If <name> already
|
||||
is set, <name> will be overridden and user will be warned.
|
||||
|
||||
### unset
|
||||
|
||||
Executing `bundle config unset <name>` will delete the configuration in both
|
||||
local and global sources.
|
||||
|
||||
**OPTIONS**
|
||||
|
||||
* `--local`:
|
||||
Executing `bundle config unset --local <name>` will delete the configuration
|
||||
only from the local application.
|
||||
|
||||
* `--global`:
|
||||
Executing `bundle config unset --global <name>` will delete the configuration
|
||||
only from the user configuration.
|
||||
|
||||
## CONFIGURATION KEYS
|
||||
|
||||
Configuration keys in bundler have two forms: the canonical form and the
|
||||
environment variable form.
|
||||
|
||||
For instance, passing the `--without` flag to [bundle install(1)](bundle-install.1.html)
|
||||
prevents Bundler from installing certain groups specified in the Gemfile(5). Bundler
|
||||
persists this value in `app/.bundle/config` so that calls to `Bundler.setup`
|
||||
do not try to find gems from the `Gemfile` that you didn't install. Additionally,
|
||||
subsequent calls to [bundle install(1)](bundle-install.1.html) remember this setting
|
||||
and skip those groups.
|
||||
|
||||
The canonical form of this configuration is `"without"`. To convert the canonical
|
||||
form to the environment variable form, capitalize it, and prepend `BUNDLE_`. The
|
||||
environment variable form of `"without"` is `BUNDLE_WITHOUT`.
|
||||
|
||||
Any periods in the configuration keys must be replaced with two underscores when
|
||||
setting it via environment variables. The configuration key `local.rack` becomes
|
||||
the environment variable `BUNDLE_LOCAL__RACK`.
|
||||
|
||||
## LIST OF AVAILABLE KEYS
|
||||
|
||||
The following is a list of all configuration keys and their purpose. You can
|
||||
learn more about their operation in [bundle install(1)](bundle-install.1.html).
|
||||
|
||||
* `api_request_size` (`BUNDLE_API_REQUEST_SIZE`):
|
||||
Configure how many dependencies to fetch when resolving the specifications.
|
||||
This configuration is only used when fetchig specifications from RubyGems
|
||||
servers that didn't implement the Compact Index API.
|
||||
Defaults to 100.
|
||||
* `auto_install` (`BUNDLE_AUTO_INSTALL`):
|
||||
Automatically run `bundle install` when gems are missing.
|
||||
* `bin` (`BUNDLE_BIN`):
|
||||
If configured, `bundle binstubs` will install executables from gems in the
|
||||
bundle to the specified directory. Otherwise it will create them in a `bin`
|
||||
directory relative to the Gemfile directory. These executables run in
|
||||
Bundler's context. If used, you might add this directory to your
|
||||
environment's `PATH` variable. For instance, if the `rails` gem comes with a
|
||||
`rails` executable, `bundle binstubs` will create a `bin/rails` executable
|
||||
that ensures that all referred dependencies will be resolved using the
|
||||
bundled gems.
|
||||
* `cache_all` (`BUNDLE_CACHE_ALL`):
|
||||
Cache all gems, including path and git gems. This needs to be explicitly
|
||||
before bundler 4, but will be the default on bundler 4.
|
||||
* `cache_all_platforms` (`BUNDLE_CACHE_ALL_PLATFORMS`):
|
||||
Cache gems for all platforms.
|
||||
* `cache_path` (`BUNDLE_CACHE_PATH`):
|
||||
The directory that bundler will place cached gems in when running
|
||||
<code>bundle package</code>, and that bundler will look in when installing gems.
|
||||
Defaults to `vendor/cache`.
|
||||
* `clean` (`BUNDLE_CLEAN`):
|
||||
Whether Bundler should run `bundle clean` automatically after
|
||||
`bundle install`. Defaults to `true` in Bundler 5, as long as `path` is not
|
||||
explicitly configured.
|
||||
* `console` (`BUNDLE_CONSOLE`):
|
||||
The console that `bundle console` starts. Defaults to `irb`.
|
||||
* `cooldown` (`BUNDLE_COOLDOWN`):
|
||||
Number of days a published gem version must age before bundler will
|
||||
resolve to it. Defaults to unset (no cooldown). Pass `0` to disable
|
||||
cooldown for an individual run.
|
||||
|
||||
The effective cooldown for any given gem is resolved from three
|
||||
layers, highest precedence first:
|
||||
|
||||
1. CLI flag `--cooldown N` on `install`, `update`, `add`, and
|
||||
`outdated`.
|
||||
2. This setting (`bundle config set cooldown N` or
|
||||
`BUNDLE_COOLDOWN=N`).
|
||||
3. The per-source `cooldown:` keyword in the Gemfile, such as
|
||||
`source "https://rubygems.org", cooldown: 7`.
|
||||
|
||||
The CLI flag and this setting apply uniformly to every source,
|
||||
including ones declared with their own `cooldown:` value. To keep a
|
||||
private registry permanently exempt while still cooling down public
|
||||
gems, declare `source "https://internal", cooldown: 0` in the
|
||||
Gemfile; remember that `--cooldown N` on the command line will
|
||||
still override it for that single run.
|
||||
|
||||
Cooldown filtering depends on the gem server providing a per-version
|
||||
`created_at` timestamp in the v2 compact-index format. Versions
|
||||
without that metadata - older gem servers, historical entries that
|
||||
predate the v2 cutover on `rubygems.org`, or private registries that
|
||||
still emit the v1 format - are treated as outside the cooldown
|
||||
window and remain resolvable. If you rely on cooldown for
|
||||
supply-chain protection, confirm that the gem server emits
|
||||
`created_at` in its `/info/<gem>` responses.
|
||||
* `default_cli_command` (`BUNDLE_DEFAULT_CLI_COMMAND`):
|
||||
The command that running `bundle` without arguments should run. Defaults to
|
||||
`cli_help` since Bundler 4, but can also be `install` which was the previous
|
||||
default.
|
||||
* `deployment` (`BUNDLE_DEPLOYMENT`):
|
||||
Equivalent to setting `frozen` to `true` and `path` to `vendor/bundle`.
|
||||
* `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`):
|
||||
Allow installing gems even if they do not match the checksum provided by
|
||||
RubyGems.
|
||||
* `disable_exec_load` (`BUNDLE_DISABLE_EXEC_LOAD`):
|
||||
Stop Bundler from using `load` to launch an executable in-process in
|
||||
`bundle exec`.
|
||||
* `disable_local_branch_check` (`BUNDLE_DISABLE_LOCAL_BRANCH_CHECK`):
|
||||
Allow Bundler to use a local git override without a branch specified in the
|
||||
Gemfile.
|
||||
* `disable_local_revision_check` (`BUNDLE_DISABLE_LOCAL_REVISION_CHECK`):
|
||||
Allow Bundler to use a local git override without checking if the revision
|
||||
present in the lockfile is present in the repository.
|
||||
* `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`):
|
||||
Stop Bundler from accessing gems installed to RubyGems' normal location.
|
||||
* `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`):
|
||||
Stop Bundler from checking if a newer Bundler version is available on
|
||||
rubygems.org.
|
||||
* `force_ruby_platform` (`BUNDLE_FORCE_RUBY_PLATFORM`):
|
||||
Ignore the current machine's platform and install only `ruby` platform gems.
|
||||
As a result, gems with native extensions will be compiled from source.
|
||||
* `frozen` (`BUNDLE_FROZEN`):
|
||||
Disallow any automatic changes to `Gemfile.lock`. Bundler commands will
|
||||
be blocked unless the lockfile can be installed exactly as written.
|
||||
Usually this will happen when changing the `Gemfile` manually and forgetting
|
||||
to update the lockfile through `bundle lock` or `bundle install`.
|
||||
* `gem.github_username` (`BUNDLE_GEM__GITHUB_USERNAME`):
|
||||
Sets a GitHub username or organization to be used in the `README` and `.gemspec` files
|
||||
when you create a new gem via `bundle gem` command. It can be overridden by passing an
|
||||
explicit `--github-username` flag to `bundle gem`.
|
||||
* `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`):
|
||||
Sets the `--key` parameter for `gem push` when using the `rake release`
|
||||
command with a private gemstash server.
|
||||
* `gemfile` (`BUNDLE_GEMFILE`):
|
||||
The name of the file that bundler should use as the `Gemfile`. This location
|
||||
of this file also sets the root of the project, which is used to resolve
|
||||
relative paths in the `Gemfile`, among other things. By default, bundler
|
||||
will search up from the current working directory until it finds a
|
||||
`Gemfile`.
|
||||
* `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`):
|
||||
Whether Bundler should cache all gems and compiled extensions globally,
|
||||
rather than locally to the configured installation path.
|
||||
* `ignore_funding_requests` (`BUNDLE_IGNORE_FUNDING_REQUESTS`):
|
||||
When set, no funding requests will be printed.
|
||||
* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`):
|
||||
When set, no post install messages will be printed. To silence a single gem,
|
||||
use dot notation like `ignore_messages.httparty true`.
|
||||
* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`):
|
||||
Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`.
|
||||
* `jobs` (`BUNDLE_JOBS`):
|
||||
The number of gems Bundler can download and install in parallel.
|
||||
Defaults to the number of available processors.
|
||||
* `lockfile` (`BUNDLE_LOCKFILE`):
|
||||
The path to the lockfile that bundler should use. By default, Bundler adds
|
||||
`.lock` to the end of the `gemfile` entry. Can be set to `false` in the
|
||||
Gemfile to disable lockfile creation entirely (see gemfile(5)).
|
||||
* `lockfile_checksums` (`BUNDLE_LOCKFILE_CHECKSUMS`):
|
||||
Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. Defaults to true.
|
||||
Bundler's own checksum is only included when its `.gem` file is cached, which
|
||||
may not be the case when Bundler is installed as a default gem.
|
||||
* `no_install` (`BUNDLE_NO_INSTALL`):
|
||||
Whether `bundle package` should skip installing gems.
|
||||
* `no_prune` (`BUNDLE_NO_PRUNE`):
|
||||
Whether Bundler should leave outdated gems unpruned when caching.
|
||||
* `only` (`BUNDLE_ONLY`):
|
||||
A space-separated list of groups to install only gems of the specified groups.
|
||||
Please check carefully if you want to install also gems without a group, because
|
||||
they get put inside `default` group. For example `only test:default` will install
|
||||
all gems specified in test group and without one.
|
||||
* `path` (`BUNDLE_PATH`):
|
||||
The location on disk where all gems in your bundle will be located regardless
|
||||
of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location
|
||||
will be installed by `bundle install`. When not set, Bundler install by
|
||||
default to a `.bundle` directory relative to repository root in Bundler 5,
|
||||
and to the default system path (`Gem.dir`) before Bundler 5. That means that
|
||||
before Bundler 5, Bundler shares this location with Rubygems, and `gem
|
||||
install ...` will have gems installed in the same location and therefore,
|
||||
gems installed without `path` set will show up by calling `gem list`. This
|
||||
will not be the case in Bundler 5.
|
||||
* `path.system` (`BUNDLE_PATH__SYSTEM`):
|
||||
Whether Bundler will install gems into the default system path (`Gem.dir`).
|
||||
* `plugins` (`BUNDLE_PLUGINS`):
|
||||
Enable Bundler's experimental plugin system.
|
||||
* `prefer_patch` (`BUNDLE_PREFER_PATCH`):
|
||||
Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`.
|
||||
* `redirect` (`BUNDLE_REDIRECT`):
|
||||
The number of redirects allowed for network requests. Defaults to `5`.
|
||||
* `retry` (`BUNDLE_RETRY`):
|
||||
The number of times to retry failed network requests. Defaults to `3`.
|
||||
* `shebang` (`BUNDLE_SHEBANG`):
|
||||
The program name that should be invoked for generated binstubs. Defaults to
|
||||
the ruby install name used to generate the binstub.
|
||||
* `silence_deprecations` (`BUNDLE_SILENCE_DEPRECATIONS`):
|
||||
Whether Bundler should silence deprecation warnings for behavior that will
|
||||
be changed in the next major version.
|
||||
* `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`):
|
||||
Silence the warning Bundler prints when installing gems as root.
|
||||
* `simulate_version` (`BUNDLE_SIMULATE_VERSION`):
|
||||
The virtual version Bundler should use for activating feature flags. Can be
|
||||
used to simulate all the new functionality that will be enabled in a future
|
||||
major version.
|
||||
* `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`):
|
||||
Path to a designated CA certificate file or folder containing multiple
|
||||
certificates for trusted CAs in PEM format.
|
||||
* `ssl_client_cert` (`BUNDLE_SSL_CLIENT_CERT`):
|
||||
Path to a designated file containing a X.509 client certificate
|
||||
and key in PEM format.
|
||||
* `ssl_verify_mode` (`BUNDLE_SSL_VERIFY_MODE`):
|
||||
The SSL verification mode Bundler uses when making HTTPS requests.
|
||||
Defaults to verify peer.
|
||||
* `system_bindir` (`BUNDLE_SYSTEM_BINDIR`):
|
||||
The location where RubyGems installs binstubs. Defaults to `Gem.bindir`.
|
||||
* `timeout` (`BUNDLE_TIMEOUT`):
|
||||
The seconds allowed before timing out for network requests. Defaults to `10`.
|
||||
* `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`):
|
||||
Require passing `--all` to `bundle update` when everything should be updated,
|
||||
and disallow passing no options to `bundle update`.
|
||||
* `user_agent` (`BUNDLE_USER_AGENT`):
|
||||
The custom user agent fragment Bundler includes in API requests.
|
||||
* `verbose` (`BUNDLE_VERBOSE`):
|
||||
Whether Bundler should print verbose output. Defaults to `false`, unless the
|
||||
`--verbose` CLI flag is used.
|
||||
* `version` (`BUNDLE_VERSION`):
|
||||
The version of Bundler to use when running under Bundler environment.
|
||||
Defaults to `lockfile`. You can also specify `system` or `x.y.z`.
|
||||
`lockfile` will use the Bundler version specified in the `Gemfile.lock`,
|
||||
`system` will use the system version of Bundler, and `x.y.z` will use
|
||||
the specified version of Bundler.
|
||||
* `with` (`BUNDLE_WITH`):
|
||||
A space-separated or `:`-separated list of groups whose gems bundler should install.
|
||||
* `without` (`BUNDLE_WITHOUT`):
|
||||
A space-separated or `:`-separated list of groups whose gems bundler should not install.
|
||||
|
||||
## BUILD OPTIONS
|
||||
|
||||
You can use `bundle config` to give Bundler the flags to pass to the gem
|
||||
installer every time bundler tries to install a particular gem.
|
||||
|
||||
A very common example, the `mysql` gem, requires Snow Leopard users to
|
||||
pass configuration flags to `gem install` to specify where to find the
|
||||
`mysql_config` executable.
|
||||
|
||||
gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
|
||||
|
||||
Since the specific location of that executable can change from machine
|
||||
to machine, you can specify these flags on a per-machine basis.
|
||||
|
||||
bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config
|
||||
|
||||
After running this command, every time bundler needs to install the
|
||||
`mysql` gem, it will pass along the flags you specified.
|
||||
|
||||
## LOCAL GIT REPOS
|
||||
|
||||
Bundler also allows you to work against a git repository locally
|
||||
instead of using the remote version. This can be achieved by setting
|
||||
up a local override:
|
||||
|
||||
bundle config set --local local.GEM_NAME /path/to/local/git/repository
|
||||
|
||||
Important: This feature only works for gems that are specified with a git
|
||||
source in your Gemfile. It does not work for gems installed from RubyGems
|
||||
or other sources. The gem must be defined with `git:` option pointing to a
|
||||
remote repository.
|
||||
|
||||
For example, if your Gemfile contains:
|
||||
|
||||
gem "rack", git: "https://github.com/rack/rack.git", branch: "main"
|
||||
|
||||
Then you can use a local Rack repository by running:
|
||||
|
||||
bundle config set --local local.rack ~/Work/git/rack
|
||||
|
||||
Now instead of checking out the remote git repository, the local
|
||||
override will be used. Similar to a path source, every time the local
|
||||
git repository change, changes will be automatically picked up by
|
||||
Bundler. This means a commit in the local git repo will update the
|
||||
revision in the `Gemfile.lock` to the local git repo revision. This
|
||||
requires the same attention as git submodules. Before pushing to
|
||||
the remote, you need to ensure the local override was pushed, otherwise
|
||||
you may point to a commit that only exists in your local machine.
|
||||
You'll also need to CGI escape your usernames and passwords as well.
|
||||
|
||||
Bundler does many checks to ensure a developer won't work with
|
||||
invalid references. Particularly, we force a developer to specify
|
||||
a branch in the `Gemfile` in order to use this feature. If the branch
|
||||
specified in the `Gemfile` and the current branch in the local git
|
||||
repository do not match, Bundler will abort. This ensures that
|
||||
a developer is always working against the correct branches, and prevents
|
||||
accidental locking to a different branch.
|
||||
|
||||
Finally, Bundler also ensures that the current revision in the
|
||||
`Gemfile.lock` exists in the local git repository. By doing this, Bundler
|
||||
forces you to fetch the latest changes in the remotes.
|
||||
|
||||
If you need to temporarily use a local version of a gem that is normally
|
||||
installed from RubyGems (not from git), use a path source instead:
|
||||
|
||||
gem "rack", path: "~/Work/git/rack"
|
||||
|
||||
## MIRRORS OF GEM SOURCES
|
||||
|
||||
Bundler supports overriding gem sources with mirrors. This allows you to
|
||||
configure rubygems.org as the gem source in your Gemfile while still using your
|
||||
mirror to fetch gems.
|
||||
|
||||
bundle config set --global mirror.SOURCE_URL MIRROR_URL
|
||||
|
||||
For example, to use a mirror of https://rubygems.org hosted at https://example.org:
|
||||
|
||||
bundle config set --global mirror.https://rubygems.org https://example.org
|
||||
|
||||
Each mirror also provides a fallback timeout setting. If the mirror does not
|
||||
respond within the fallback timeout, Bundler will try to use the original
|
||||
server instead of the mirror.
|
||||
|
||||
bundle config set --global mirror.SOURCE_URL.fallback_timeout TIMEOUT
|
||||
|
||||
For example, to fall back to rubygems.org after 3 seconds:
|
||||
|
||||
bundle config set --global mirror.https://rubygems.org.fallback_timeout 3
|
||||
|
||||
The default fallback timeout is 0.1 seconds, but the setting can currently
|
||||
only accept whole seconds (for example, 1, 15, or 30).
|
||||
|
||||
## CREDENTIALS FOR GEM SOURCES
|
||||
|
||||
Bundler allows you to configure credentials for any gem source, which allows
|
||||
you to avoid putting secrets into your Gemfile.
|
||||
|
||||
bundle config set --global SOURCE_HOSTNAME USERNAME:PASSWORD
|
||||
|
||||
For example, to save the credentials of user `claudette` for the gem source at
|
||||
`gems.longerous.com`, you would run:
|
||||
|
||||
bundle config set --global gems.longerous.com claudette:s00pers3krit
|
||||
|
||||
Or you can set the credentials as an environment variable like this:
|
||||
|
||||
export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit"
|
||||
|
||||
For gems with a git source with HTTP(S) URL you can specify credentials like so:
|
||||
|
||||
bundle config set --global https://github.com/ruby/rubygems.git username:password
|
||||
|
||||
Or you can set the credentials as an environment variable like so:
|
||||
|
||||
export BUNDLE_GITHUB__COM=username:password
|
||||
|
||||
This is especially useful for private repositories on hosts such as GitHub,
|
||||
where you can use personal OAuth tokens:
|
||||
|
||||
export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x-oauth-basic
|
||||
|
||||
Note that any configured credentials will be redacted by informative commands
|
||||
such as `bundle config list` or `bundle config get`, unless you use the
|
||||
`--parseable` flag. This is to avoid unintentionally leaking credentials when
|
||||
copy-pasting bundler output.
|
||||
|
||||
Also note that to guarantee a sane mapping between valid environment variable
|
||||
names and valid host names, bundler makes the following transformations:
|
||||
|
||||
* Any `-` characters in a host name are mapped to a triple underscore (`___`) in the
|
||||
corresponding environment variable.
|
||||
|
||||
* Any `.` characters in a host name are mapped to a double underscore (`__`) in the
|
||||
corresponding environment variable.
|
||||
|
||||
This means that if you have a gem server named `my.gem-host.com`, you'll need to
|
||||
use the `BUNDLE_MY__GEM___HOST__COM` variable to configure credentials for it
|
||||
through ENV.
|
||||
|
||||
## CONFIGURE BUNDLER DIRECTORIES
|
||||
|
||||
Bundler's home, cache and plugin directories and config file can be configured
|
||||
through environment variables. The default location for Bundler's home directory is
|
||||
`~/.bundle`, which all directories inherit from by default. The following
|
||||
outlines the available environment variables and their default values
|
||||
|
||||
BUNDLE_USER_HOME : $HOME/.bundle
|
||||
BUNDLE_USER_CACHE : $BUNDLE_USER_HOME/cache
|
||||
BUNDLE_USER_CONFIG : $BUNDLE_USER_HOME/config
|
||||
BUNDLE_USER_PLUGIN : $BUNDLE_USER_HOME/plugin
|
||||
@@ -0,0 +1,33 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-CONSOLE" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle console\fR [GROUP]
|
||||
.SH "DESCRIPTION"
|
||||
Starts an interactive Ruby console session in the context of the current bundle\.
|
||||
.P
|
||||
If no \fBGROUP\fR is specified, all gems in the \fBdefault\fR group in the Gemfile(5) \fIhttps://guides\.rubygems\.org/gemfile/\fR are preliminarily loaded\.
|
||||
.P
|
||||
If \fBGROUP\fR is specified, all gems in the given group in the Gemfile in addition to the gems in \fBdefault\fR group are loaded\. Even if the given group does not exist in the Gemfile, IRB console starts without any warning or error\.
|
||||
.P
|
||||
The environment variable \fBBUNDLE_CONSOLE\fR or \fBbundle config set console\fR can be used to change the shell from the following:
|
||||
.IP "\(bu" 4
|
||||
\fBirb\fR (default)
|
||||
.IP "\(bu" 4
|
||||
\fBpry\fR (https://github\.com/pry/pry)
|
||||
.IP "\(bu" 4
|
||||
\fBripl\fR (https://github\.com/cldwalker/ripl)
|
||||
.IP "" 0
|
||||
.P
|
||||
\fBbundle console\fR uses irb by default\. An alternative Pry or Ripl can be used with \fBbundle console\fR by adjusting the \fBconsole\fR Bundler setting\. Also make sure that \fBpry\fR or \fBripl\fR is in your Gemfile\.
|
||||
.SH "EXAMPLE"
|
||||
.nf
|
||||
$ bundle config set console pry
|
||||
$ bundle console
|
||||
Resolving dependencies\|\.\|\.\|\.
|
||||
[1] pry(main)>
|
||||
.fi
|
||||
.SH "SEE ALSO"
|
||||
Gemfile(5) \fIhttps://guides\.rubygems\.org/gemfile/\fR
|
||||
@@ -0,0 +1,39 @@
|
||||
bundle-console(1) -- Open an IRB session with the bundle pre-loaded
|
||||
===================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle console` [GROUP]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Starts an interactive Ruby console session in the context of the current bundle.
|
||||
|
||||
If no `GROUP` is specified, all gems in the `default` group in the [Gemfile(5)](https://guides.rubygems.org/gemfile/) are
|
||||
preliminarily loaded.
|
||||
|
||||
If `GROUP` is specified, all gems in the given group in the Gemfile in addition
|
||||
to the gems in `default` group are loaded. Even if the given group does not
|
||||
exist in the Gemfile, IRB console starts without any warning or error.
|
||||
|
||||
The environment variable `BUNDLE_CONSOLE` or `bundle config set console` can be used to change
|
||||
the shell from the following:
|
||||
|
||||
* `irb` (default)
|
||||
* `pry` (https://github.com/pry/pry)
|
||||
* `ripl` (https://github.com/cldwalker/ripl)
|
||||
|
||||
`bundle console` uses irb by default. An alternative Pry or Ripl can be used with
|
||||
`bundle console` by adjusting the `console` Bundler setting. Also make sure that
|
||||
`pry` or `ripl` is in your Gemfile.
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
$ bundle config set console pry
|
||||
$ bundle console
|
||||
Resolving dependencies...
|
||||
[1] pry(main)>
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
[Gemfile(5)](https://guides.rubygems.org/gemfile/)
|
||||
@@ -0,0 +1,69 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-DOCTOR" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-doctor\fR \- Checks the bundle for common problems
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle doctor [diagnose]\fR [\-\-quiet] [\-\-gemfile=GEMFILE] [\-\-ssl]
|
||||
.br
|
||||
\fBbundle doctor ssl\fR [\-\-host=HOST] [\-\-tls\-version=TLS\-VERSION] [\-\-verify\-mode=VERIFY\-MODE]
|
||||
.br
|
||||
\fBbundle doctor\fR help [COMMAND]
|
||||
.SH "DESCRIPTION"
|
||||
You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue\.
|
||||
.SH "SUB\-COMMANDS"
|
||||
.SS "diagnose (default command)"
|
||||
Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\.
|
||||
.P
|
||||
Examples of common problems caught include:
|
||||
.IP "\(bu" 4
|
||||
Invalid Bundler settings
|
||||
.IP "\(bu" 4
|
||||
Mismatched Ruby versions
|
||||
.IP "\(bu" 4
|
||||
Mismatched platforms
|
||||
.IP "\(bu" 4
|
||||
Uninstalled gems
|
||||
.IP "\(bu" 4
|
||||
Missing dependencies
|
||||
.IP "" 0
|
||||
.P
|
||||
\fBOPTIONS\fR
|
||||
.TP
|
||||
\fB\-\-quiet\fR
|
||||
Only output warnings and errors\.
|
||||
.TP
|
||||
\fB\-\-gemfile=GEMFILE\fR
|
||||
The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\.
|
||||
.TP
|
||||
\fB\-\-ssl\fR
|
||||
Diagnose common SSL problems when connecting to https://rubygems\.org\.
|
||||
.IP
|
||||
This flag runs the \fBbundle doctor ssl\fR subcommand with default values underneath\.
|
||||
.SS "ssl"
|
||||
If you've experienced issues related to SSL certificates and/or TLS versions while connecting to https://rubygems\.org, this command can help troubleshoot common problems\. The diagnostic will perform a few checks such as:
|
||||
.IP "\(bu" 4
|
||||
Verify the Ruby OpenSSL version installed on your system\.
|
||||
.IP "\(bu" 4
|
||||
Check the OpenSSL library version used for compilation\.
|
||||
.IP "\(bu" 4
|
||||
Ensure CA certificates are correctly setup on your machine\.
|
||||
.IP "\(bu" 4
|
||||
Open a TLS connection and verify the outcome\.
|
||||
.IP "" 0
|
||||
.P
|
||||
\fBOPTIONS\fR
|
||||
.TP
|
||||
\fB\-\-host=HOST\fR
|
||||
Perform the diagnostic on HOST\. Defaults to \fBrubygems\.org\fR\.
|
||||
.TP
|
||||
\fB\-\-tls\-version=TLS\-VERSION\fR
|
||||
Specify the TLS version when opening the connection to HOST\.
|
||||
.IP
|
||||
Accepted values are: \fB1\.1\fR or \fB1\.2\fR\.
|
||||
.TP
|
||||
\fB\-\-verify\-mode=VERIFY\-MODE\fR
|
||||
Specify the TLS verify mode when opening the connection to HOST\. Defaults to \fBSSL_VERIFY_PEER\fR\.
|
||||
.IP
|
||||
Accepted values are: \fBCLIENT_ONCE\fR, \fBFAIL_IF_NO_PEER_CERT\fR, \fBNONE\fR, \fBPEER\fR\.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
bundle-doctor(1) -- Checks the bundle for common problems
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle doctor [diagnose]` [--quiet]
|
||||
[--gemfile=GEMFILE]
|
||||
[--ssl]<br>
|
||||
`bundle doctor ssl` [--host=HOST]
|
||||
[--tls-version=TLS-VERSION]
|
||||
[--verify-mode=VERIFY-MODE]<br>
|
||||
`bundle doctor` help [COMMAND]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue.
|
||||
|
||||
## SUB-COMMANDS
|
||||
|
||||
### diagnose (default command)
|
||||
|
||||
Checks your Gemfile and gem environment for common problems. If issues
|
||||
are detected, Bundler prints them and exits status 1. Otherwise,
|
||||
Bundler prints a success message and exits status 0.
|
||||
|
||||
Examples of common problems caught include:
|
||||
|
||||
* Invalid Bundler settings
|
||||
* Mismatched Ruby versions
|
||||
* Mismatched platforms
|
||||
* Uninstalled gems
|
||||
* Missing dependencies
|
||||
|
||||
**OPTIONS**
|
||||
|
||||
* `--quiet`:
|
||||
Only output warnings and errors.
|
||||
|
||||
* `--gemfile=GEMFILE`:
|
||||
The location of the Gemfile(5) which Bundler should use. This defaults
|
||||
to a Gemfile(5) in the current working directory. In general, Bundler
|
||||
will assume that the location of the Gemfile(5) is also the project's
|
||||
root and will try to find `Gemfile.lock` and `vendor/cache` relative
|
||||
to this location.
|
||||
|
||||
* `--ssl`:
|
||||
Diagnose common SSL problems when connecting to https://rubygems.org.
|
||||
|
||||
This flag runs the `bundle doctor ssl` subcommand with default values
|
||||
underneath.
|
||||
|
||||
### ssl
|
||||
|
||||
If you've experienced issues related to SSL certificates and/or TLS versions while connecting
|
||||
to https://rubygems.org, this command can help troubleshoot common problems.
|
||||
The diagnostic will perform a few checks such as:
|
||||
|
||||
* Verify the Ruby OpenSSL version installed on your system.
|
||||
* Check the OpenSSL library version used for compilation.
|
||||
* Ensure CA certificates are correctly setup on your machine.
|
||||
* Open a TLS connection and verify the outcome.
|
||||
|
||||
**OPTIONS**
|
||||
|
||||
* `--host=HOST`:
|
||||
Perform the diagnostic on HOST. Defaults to `rubygems.org`.
|
||||
|
||||
* `--tls-version=TLS-VERSION`:
|
||||
Specify the TLS version when opening the connection to HOST.
|
||||
|
||||
Accepted values are: `1.1` or `1.2`.
|
||||
|
||||
* `--verify-mode=VERIFY-MODE`:
|
||||
Specify the TLS verify mode when opening the connection to HOST.
|
||||
Defaults to `SSL_VERIFY_PEER`.
|
||||
|
||||
Accepted values are: `CLIENT_ONCE`, `FAIL_IF_NO_PEER_CERT`, `NONE`, `PEER`.
|
||||
@@ -0,0 +1,9 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-ENV" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-env\fR \- Print information about the environment Bundler is running under
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle env\fR
|
||||
.SH "DESCRIPTION"
|
||||
Prints information about the environment Bundler is running under\.
|
||||
@@ -0,0 +1,10 @@
|
||||
bundle-env(1) -- Print information about the environment Bundler is running under
|
||||
=================================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle env`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Prints information about the environment Bundler is running under.
|
||||
@@ -0,0 +1,104 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-EXEC" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-exec\fR \- Execute a command in the context of the bundle
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle exec\fR [\-\-gemfile=GEMFILE] \fIcommand\fR
|
||||
.SH "DESCRIPTION"
|
||||
This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\.
|
||||
.P
|
||||
Essentially, if you would normally have run something like \fBrspec spec/my_spec\.rb\fR, and you want to use the gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] and installed via bundle install(1) \fIbundle\-install\.1\.html\fR, you should run \fBbundle exec rspec spec/my_spec\.rb\fR\.
|
||||
.P
|
||||
Note that \fBbundle exec\fR does not require that an executable is available on your shell's \fB$PATH\fR\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-\-gemfile=GEMFILE\fR
|
||||
Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\.
|
||||
.SH "BUNDLE INSTALL \-\-BINSTUBS"
|
||||
If you use the \fB\-\-binstubs\fR flag in bundle install(1) \fIbundle\-install\.1\.html\fR, Bundler will automatically create a directory (which defaults to \fBapp_root/bin\fR) containing all of the executables available from gems in the bundle\.
|
||||
.P
|
||||
After using \fB\-\-binstubs\fR, \fBbin/rspec spec/my_spec\.rb\fR is identical to \fBbundle exec rspec spec/my_spec\.rb\fR\.
|
||||
.SH "ENVIRONMENT MODIFICATIONS"
|
||||
\fBbundle exec\fR makes a number of changes to the shell environment, then executes the command you specify in full\.
|
||||
.IP "\(bu" 4
|
||||
make sure that it's still possible to shell out to \fBbundle\fR from inside a command invoked by \fBbundle exec\fR (using \fB$BUNDLE_BIN_PATH\fR)
|
||||
.IP "\(bu" 4
|
||||
put the directory containing executables (like \fBrails\fR, \fBrspec\fR, \fBrackup\fR) for your bundle on \fB$PATH\fR
|
||||
.IP "\(bu" 4
|
||||
make sure that if bundler is invoked in the subshell, it uses the same \fBGemfile\fR (by setting \fBBUNDLE_GEMFILE\fR)
|
||||
.IP "\(bu" 4
|
||||
add \fB\-rbundler/setup\fR to \fB$RUBYOPT\fR, which makes sure that Ruby programs invoked in the subshell can see the gems in the bundle
|
||||
.IP "" 0
|
||||
.P
|
||||
It also modifies Rubygems:
|
||||
.IP "\(bu" 4
|
||||
disallow loading additional gems not in the bundle
|
||||
.IP "\(bu" 4
|
||||
modify the \fBgem\fR method to be a no\-op if a gem matching the requirements is in the bundle, and to raise a \fBGem::LoadError\fR if it's not
|
||||
.IP "\(bu" 4
|
||||
Define \fBGem\.refresh\fR to be a no\-op, since the source index is always frozen when using bundler, and to prevent gems from the system leaking into the environment
|
||||
.IP "\(bu" 4
|
||||
Override \fBGem\.bin_path\fR to use the gems in the bundle, making system executables work
|
||||
.IP "\(bu" 4
|
||||
Add all gems in the bundle into Gem\.loaded_specs
|
||||
.IP "" 0
|
||||
.P
|
||||
Finally, \fBbundle exec\fR also implicitly modifies \fBGemfile\.lock\fR if the lockfile and the Gemfile do not match\. Bundler needs the Gemfile to determine things such as a gem's groups, \fBautorequire\fR, and platforms, etc\., and that information isn't stored in the lockfile\. The Gemfile and lockfile must be synced in order to \fBbundle exec\fR successfully, so \fBbundle exec\fR updates the lockfile beforehand\.
|
||||
.SS "Loading"
|
||||
By default, when attempting to \fBbundle exec\fR to a file with a ruby shebang, Bundler will \fBKernel\.load\fR that file instead of using \fBKernel\.exec\fR\. For the vast majority of cases, this is a performance improvement\. In a rare few cases, this could cause some subtle side\-effects (such as dependence on the exact contents of \fB$0\fR or \fB__FILE__\fR) and the optimization can be disabled by enabling the \fBdisable_exec_load\fR setting\.
|
||||
.SS "Shelling out"
|
||||
Any Ruby code that opens a subshell (like \fBsystem\fR, backticks, or \fB%x{}\fR) will automatically use the current Bundler environment\. If you need to shell out to a Ruby command that is not part of your current bundle, use the \fBwith_unbundled_env\fR method with a block\. Any subshells created inside the block will be given the environment present before Bundler was activated\. For example, Homebrew commands run Ruby, but don't work inside a bundle:
|
||||
.IP "" 4
|
||||
.nf
|
||||
Bundler\.with_unbundled_env do
|
||||
`brew install wget`
|
||||
end
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Using \fBwith_unbundled_env\fR is also necessary if you are shelling out to a different bundle\. Any Bundler commands run in a subshell will inherit the current Gemfile, so commands that need to run in the context of a different bundle also need to use \fBwith_unbundled_env\fR\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
Bundler\.with_unbundled_env do
|
||||
Dir\.chdir "/other/bundler/project" do
|
||||
`bundle exec \./script`
|
||||
end
|
||||
end
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Bundler provides convenience helpers that wrap \fBsystem\fR and \fBexec\fR, and they can be used like this:
|
||||
.IP "" 4
|
||||
.nf
|
||||
Bundler\.unbundled_system('brew install wget')
|
||||
Bundler\.unbundled_exec('brew install wget')
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SH "RUBYGEMS PLUGINS"
|
||||
At present, the Rubygems plugin system requires all files named \fBrubygems_plugin\.rb\fR on the load path of \fIany\fR installed gem when any Ruby code requires \fBrubygems\.rb\fR\. This includes executables installed into the system, like \fBrails\fR, \fBrackup\fR, and \fBrspec\fR\.
|
||||
.P
|
||||
Since Rubygems plugins can contain arbitrary Ruby code, they commonly end up activating themselves or their dependencies\.
|
||||
.P
|
||||
For instance, the \fBgemcutter 0\.5\fR gem depended on \fBjson_pure\fR\. If you had that version of gemcutter installed (even if you \fIalso\fR had a newer version without this problem), Rubygems would activate \fBgemcutter 0\.5\fR and \fBjson_pure <latest>\fR\.
|
||||
.P
|
||||
If your Gemfile(5) also contained \fBjson_pure\fR (or a gem with a dependency on \fBjson_pure\fR), the latest version on your system might conflict with the version in your Gemfile(5), or the snapshot version in your \fBGemfile\.lock\fR\.
|
||||
.P
|
||||
If this happens, bundler will say:
|
||||
.IP "" 4
|
||||
.nf
|
||||
You have already activated json_pure 1\.4\.6 but your Gemfile
|
||||
requires json_pure 1\.4\.3\. Consider using bundle exec\.
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
In this situation, you almost certainly want to remove the underlying gem with the problematic gem plugin\. In general, the authors of these plugins (in this case, the \fBgemcutter\fR gem) have released newer versions that are more careful in their plugins\.
|
||||
.P
|
||||
You can find a list of all the gems containing gem plugins by running
|
||||
.IP "" 4
|
||||
.nf
|
||||
ruby \-e "puts Gem\.find_files('rubygems_plugin\.rb')"
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
At the very least, you should remove all but the newest version of each gem plugin, and also remove all gem plugins that you aren't using (\fBgem uninstall gem_name\fR)\.
|
||||
@@ -0,0 +1,150 @@
|
||||
bundle-exec(1) -- Execute a command in the context of the bundle
|
||||
================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle exec` [--gemfile=GEMFILE] <command>
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
This command executes the command, making all gems specified in the
|
||||
[`Gemfile(5)`][Gemfile(5)] available to `require` in Ruby programs.
|
||||
|
||||
Essentially, if you would normally have run something like
|
||||
`rspec spec/my_spec.rb`, and you want to use the gems specified
|
||||
in the [`Gemfile(5)`][Gemfile(5)] and installed via [bundle install(1)](bundle-install.1.html), you
|
||||
should run `bundle exec rspec spec/my_spec.rb`.
|
||||
|
||||
Note that `bundle exec` does not require that an executable is
|
||||
available on your shell's `$PATH`.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `--gemfile=GEMFILE`:
|
||||
Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)].
|
||||
|
||||
## BUNDLE INSTALL --BINSTUBS
|
||||
|
||||
If you use the `--binstubs` flag in [bundle install(1)](bundle-install.1.html), Bundler will
|
||||
automatically create a directory (which defaults to `app_root/bin`)
|
||||
containing all of the executables available from gems in the bundle.
|
||||
|
||||
After using `--binstubs`, `bin/rspec spec/my_spec.rb` is identical
|
||||
to `bundle exec rspec spec/my_spec.rb`.
|
||||
|
||||
## ENVIRONMENT MODIFICATIONS
|
||||
|
||||
`bundle exec` makes a number of changes to the shell environment,
|
||||
then executes the command you specify in full.
|
||||
|
||||
* make sure that it's still possible to shell out to `bundle`
|
||||
from inside a command invoked by `bundle exec` (using
|
||||
`$BUNDLE_BIN_PATH`)
|
||||
* put the directory containing executables (like `rails`, `rspec`,
|
||||
`rackup`) for your bundle on `$PATH`
|
||||
* make sure that if bundler is invoked in the subshell, it uses
|
||||
the same `Gemfile` (by setting `BUNDLE_GEMFILE`)
|
||||
* add `-rbundler/setup` to `$RUBYOPT`, which makes sure that
|
||||
Ruby programs invoked in the subshell can see the gems in
|
||||
the bundle
|
||||
|
||||
It also modifies Rubygems:
|
||||
|
||||
* disallow loading additional gems not in the bundle
|
||||
* modify the `gem` method to be a no-op if a gem matching
|
||||
the requirements is in the bundle, and to raise a
|
||||
`Gem::LoadError` if it's not
|
||||
* Define `Gem.refresh` to be a no-op, since the source
|
||||
index is always frozen when using bundler, and to
|
||||
prevent gems from the system leaking into the environment
|
||||
* Override `Gem.bin_path` to use the gems in the bundle,
|
||||
making system executables work
|
||||
* Add all gems in the bundle into Gem.loaded_specs
|
||||
|
||||
Finally, `bundle exec` also implicitly modifies `Gemfile.lock` if the lockfile
|
||||
and the Gemfile do not match. Bundler needs the Gemfile to determine things
|
||||
such as a gem's groups, `autorequire`, and platforms, etc., and that
|
||||
information isn't stored in the lockfile. The Gemfile and lockfile must be
|
||||
synced in order to `bundle exec` successfully, so `bundle exec`
|
||||
updates the lockfile beforehand.
|
||||
|
||||
### Loading
|
||||
|
||||
By default, when attempting to `bundle exec` to a file with a ruby shebang,
|
||||
Bundler will `Kernel.load` that file instead of using `Kernel.exec`. For the
|
||||
vast majority of cases, this is a performance improvement. In a rare few cases,
|
||||
this could cause some subtle side-effects (such as dependence on the exact
|
||||
contents of `$0` or `__FILE__`) and the optimization can be disabled by enabling
|
||||
the `disable_exec_load` setting.
|
||||
|
||||
### Shelling out
|
||||
|
||||
Any Ruby code that opens a subshell (like `system`, backticks, or `%x{}`) will
|
||||
automatically use the current Bundler environment. If you need to shell out to
|
||||
a Ruby command that is not part of your current bundle, use the
|
||||
`with_unbundled_env` method with a block. Any subshells created inside the block
|
||||
will be given the environment present before Bundler was activated. For
|
||||
example, Homebrew commands run Ruby, but don't work inside a bundle:
|
||||
|
||||
Bundler.with_unbundled_env do
|
||||
`brew install wget`
|
||||
end
|
||||
|
||||
Using `with_unbundled_env` is also necessary if you are shelling out to a different
|
||||
bundle. Any Bundler commands run in a subshell will inherit the current
|
||||
Gemfile, so commands that need to run in the context of a different bundle also
|
||||
need to use `with_unbundled_env`.
|
||||
|
||||
Bundler.with_unbundled_env do
|
||||
Dir.chdir "/other/bundler/project" do
|
||||
`bundle exec ./script`
|
||||
end
|
||||
end
|
||||
|
||||
Bundler provides convenience helpers that wrap `system` and `exec`, and they
|
||||
can be used like this:
|
||||
|
||||
Bundler.unbundled_system('brew install wget')
|
||||
Bundler.unbundled_exec('brew install wget')
|
||||
|
||||
|
||||
## RUBYGEMS PLUGINS
|
||||
|
||||
At present, the Rubygems plugin system requires all files
|
||||
named `rubygems_plugin.rb` on the load path of _any_ installed
|
||||
gem when any Ruby code requires `rubygems.rb`. This includes
|
||||
executables installed into the system, like `rails`, `rackup`,
|
||||
and `rspec`.
|
||||
|
||||
Since Rubygems plugins can contain arbitrary Ruby code, they
|
||||
commonly end up activating themselves or their dependencies.
|
||||
|
||||
For instance, the `gemcutter 0.5` gem depended on `json_pure`.
|
||||
If you had that version of gemcutter installed (even if
|
||||
you _also_ had a newer version without this problem), Rubygems
|
||||
would activate `gemcutter 0.5` and `json_pure <latest>`.
|
||||
|
||||
If your Gemfile(5) also contained `json_pure` (or a gem
|
||||
with a dependency on `json_pure`), the latest version on
|
||||
your system might conflict with the version in your
|
||||
Gemfile(5), or the snapshot version in your `Gemfile.lock`.
|
||||
|
||||
If this happens, bundler will say:
|
||||
|
||||
You have already activated json_pure 1.4.6 but your Gemfile
|
||||
requires json_pure 1.4.3. Consider using bundle exec.
|
||||
|
||||
In this situation, you almost certainly want to remove the
|
||||
underlying gem with the problematic gem plugin. In general,
|
||||
the authors of these plugins (in this case, the `gemcutter`
|
||||
gem) have released newer versions that are more careful in
|
||||
their plugins.
|
||||
|
||||
You can find a list of all the gems containing gem plugins
|
||||
by running
|
||||
|
||||
ruby -e "puts Gem.find_files('rubygems_plugin.rb')"
|
||||
|
||||
At the very least, you should remove all but the newest
|
||||
version of each gem plugin, and also remove all gem plugins
|
||||
that you aren't using (`gem uninstall gem_name`).
|
||||
@@ -0,0 +1,22 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-FUND" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-fund\fR \- Lists information about gems seeking funding assistance
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle fund\fR [\fIOPTIONS\fR]
|
||||
.SH "DESCRIPTION"
|
||||
\fBbundle fund\fR lists information about gems seeking funding assistance\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-\-group=<list>\fR, \fB\-g=<list>\fR
|
||||
Fetch funding information for a specific group\.
|
||||
.SH "EXAMPLES"
|
||||
.nf
|
||||
# Lists funding information for all gems
|
||||
bundle fund
|
||||
|
||||
# Lists funding information for a specific group
|
||||
bundle fund \-\-group=security
|
||||
.fi
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
bundle-fund(1) -- Lists information about gems seeking funding assistance
|
||||
=========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle fund` [*OPTIONS*]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**bundle fund** lists information about gems seeking funding assistance.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `--group=<list>`, `-g=<list>`:
|
||||
Fetch funding information for a specific group.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# Lists funding information for all gems
|
||||
bundle fund
|
||||
|
||||
# Lists funding information for a specific group
|
||||
bundle fund --group=security
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
.\" generated with Ronn-NG/v0.10.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
||||
.TH "BUNDLE\-GEM" "1" "April 2026" ""
|
||||
.SH "NAME"
|
||||
\fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem
|
||||
.SH "SYNOPSIS"
|
||||
\fBbundle gem\fR \fIGEM_NAME\fR \fIOPTIONS\fR
|
||||
.SH "DESCRIPTION"
|
||||
Generates a directory named \fBGEM_NAME\fR with a \fBRakefile\fR, \fBGEM_NAME\.gemspec\fR, and other supporting files and directories that can be used to develop a rubygem with that name\.
|
||||
.P
|
||||
Run \fBrake \-T\fR in the resulting project for a list of Rake tasks that can be used to test and publish the gem to rubygems\.org\.
|
||||
.P
|
||||
The generated project skeleton can be customized with OPTIONS, as explained below\. Note that these options can also be specified via Bundler's global configuration file using the following names:
|
||||
.IP "\(bu" 4
|
||||
\fBgem\.coc\fR
|
||||
.IP "\(bu" 4
|
||||
\fBgem\.mit\fR
|
||||
.IP "\(bu" 4
|
||||
\fBgem\.test\fR
|
||||
.IP "" 0
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR
|
||||
Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\.
|
||||
.TP
|
||||
\fB\-\-no\-exe\fR
|
||||
Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-\-coc\fR
|
||||
Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
|
||||
.TP
|
||||
\fB\-\-no\-coc\fR
|
||||
Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-\-changelog\fR
|
||||
Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. Update the default with \fBbundle config set \-\-global gem\.changelog <true|false>\fR\.
|
||||
.TP
|
||||
\fB\-\-no\-changelog\fR
|
||||
Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-\-ext=c\fR, \fB\-\-ext=go\fR, \fB\-\-ext=rust\fR
|
||||
Add boilerplate for C, Go (currently go\-gem\-wrapper \fIhttps://github\.com/ruby\-go\-gem/go\-gem\-wrapper\fR based) or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\.
|
||||
.TP
|
||||
\fB\-\-no\-ext\fR
|
||||
Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-\-git\fR
|
||||
Initialize a git repo inside your library\.
|
||||
.TP
|
||||
\fB\-\-github\-username=GITHUB_USERNAME\fR
|
||||
Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username <your_username>\fR\.
|
||||
.TP
|
||||
\fB\-\-mit\fR
|
||||
Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
|
||||
.TP
|
||||
\fB\-\-no\-mit\fR
|
||||
Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR
|
||||
Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified:
|
||||
.IP
|
||||
When Bundler is configured to generate tests, this defaults to Bundler's global config setting \fBgem\.test\fR\.
|
||||
.IP
|
||||
When Bundler is configured to not generate tests, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
|
||||
.IP
|
||||
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
|
||||
.TP
|
||||
\fB\-\-no\-test\fR
|
||||
Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR
|
||||
Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
|
||||
.IP
|
||||
When Bundler is configured to generate CI files, this defaults to Bundler's global config setting \fBgem\.ci\fR\.
|
||||
.IP
|
||||
When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
|
||||
.IP
|
||||
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
|
||||
.TP
|
||||
\fB\-\-no\-ci\fR
|
||||
Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR
|
||||
Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
|
||||
.IP
|
||||
When Bundler is configured to add a linter, this defaults to Bundler's global config setting \fBgem\.linter\fR\.
|
||||
.IP
|
||||
When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
|
||||
.IP
|
||||
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
|
||||
.TP
|
||||
\fB\-\-no\-linter\fR
|
||||
Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR
|
||||
Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\.
|
||||
.TP
|
||||
\fB\-\-bundle\fR
|
||||
Run \fBbundle install\fR after creating the gem\.
|
||||
.TP
|
||||
\fB\-\-no\-bundle\fR
|
||||
Do not run \fBbundle install\fR after creating the gem\.
|
||||
.SH "SEE ALSO"
|
||||
.IP "\(bu" 4
|
||||
bundle config(1) \fIbundle\-config\.1\.html\fR
|
||||
.IP "" 0
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
bundle-gem(1) -- Generate a project skeleton for creating a rubygem
|
||||
===================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`bundle gem` <GEM_NAME> [OPTIONS]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Generates a directory named `GEM_NAME` with a `Rakefile`, `GEM_NAME.gemspec`,
|
||||
and other supporting files and directories that can be used to develop a
|
||||
rubygem with that name.
|
||||
|
||||
Run `rake -T` in the resulting project for a list of Rake tasks that can be used
|
||||
to test and publish the gem to rubygems.org.
|
||||
|
||||
The generated project skeleton can be customized with OPTIONS, as explained
|
||||
below. Note that these options can also be specified via Bundler's global
|
||||
configuration file using the following names:
|
||||
|
||||
* `gem.coc`
|
||||
* `gem.mit`
|
||||
* `gem.test`
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `--exe`, `--bin`, `-b`:
|
||||
Specify that Bundler should create a binary executable (as `exe/GEM_NAME`)
|
||||
in the generated rubygem project. This binary will also be added to the
|
||||
`GEM_NAME.gemspec` manifest. This behavior is disabled by default.
|
||||
|
||||
* `--no-exe`:
|
||||
Do not create a binary (overrides `--exe` specified in the global config).
|
||||
|
||||
* `--coc`:
|
||||
Add a `CODE_OF_CONDUCT.md` file to the root of the generated project. If
|
||||
this option is unspecified, an interactive prompt will be displayed and the
|
||||
answer will be saved in Bundler's global config for future `bundle gem` use.
|
||||
|
||||
* `--no-coc`:
|
||||
Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the
|
||||
global config).
|
||||
|
||||
* `--changelog`:
|
||||
Add a `CHANGELOG.md` file to the root of the generated project. If
|
||||
this option is unspecified, an interactive prompt will be displayed and the
|
||||
answer will be saved in Bundler's global config for future `bundle gem` use.
|
||||
Update the default with `bundle config set --global gem.changelog <true|false>`.
|
||||
|
||||
* `--no-changelog`:
|
||||
Do not create a `CHANGELOG.md` (overrides `--changelog` specified in the
|
||||
global config).
|
||||
|
||||
* `--ext=c`, `--ext=go`, `--ext=rust`:
|
||||
Add boilerplate for C, Go (currently [go-gem-wrapper](https://github.com/ruby-go-gem/go-gem-wrapper) based) or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior
|
||||
is disabled by default.
|
||||
|
||||
* `--no-ext`:
|
||||
Do not add extension code (overrides `--ext` specified in the global
|
||||
config).
|
||||
|
||||
* `--git`:
|
||||
Initialize a git repo inside your library.
|
||||
|
||||
* `--github-username=GITHUB_USERNAME`:
|
||||
Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username <your_username>`.
|
||||
|
||||
* `--mit`:
|
||||
Add an MIT license to a `LICENSE.txt` file in the root of the generated
|
||||
project. Your name from the global git config is used for the copyright
|
||||
statement. If this option is unspecified, an interactive prompt will be
|
||||
displayed and the answer will be saved in Bundler's global config for future
|
||||
`bundle gem` use.
|
||||
|
||||
* `--no-mit`:
|
||||
Do not create a `LICENSE.txt` (overrides `--mit` specified in the global
|
||||
config).
|
||||
|
||||
* `-t`, `--test=minitest`, `--test=rspec`, `--test=test-unit`:
|
||||
Specify the test framework that Bundler should use when generating the
|
||||
project. Acceptable values are `minitest`, `rspec` and `test-unit`. The
|
||||
`GEM_NAME.gemspec` will be configured and a skeleton test/spec directory will
|
||||
be created based on this option. Given no option is specified:
|
||||
|
||||
When Bundler is configured to generate tests, this defaults to Bundler's
|
||||
global config setting `gem.test`.
|
||||
|
||||
When Bundler is configured to not generate tests, an interactive prompt will
|
||||
be displayed and the answer will be used for the current rubygem project.
|
||||
|
||||
When Bundler is unconfigured, an interactive prompt will be displayed and
|
||||
the answer will be saved in Bundler's global config for future `bundle gem`
|
||||
use.
|
||||
|
||||
* `--no-test`:
|
||||
Do not use a test framework (overrides `--test` specified in the global
|
||||
config).
|
||||
|
||||
* `--ci`, `--ci=circle`, `--ci=github`, `--ci=gitlab`:
|
||||
Specify the continuous integration service that Bundler should use when
|
||||
generating the project. Acceptable values are `github`, `gitlab`
|
||||
and `circle`. A configuration file will be generated in the project directory.
|
||||
Given no option is specified:
|
||||
|
||||
When Bundler is configured to generate CI files, this defaults to Bundler's
|
||||
global config setting `gem.ci`.
|
||||
|
||||
When Bundler is configured to not generate CI files, an interactive prompt
|
||||
will be displayed and the answer will be used for the current rubygem project.
|
||||
|
||||
When Bundler is unconfigured, an interactive prompt will be displayed and
|
||||
the answer will be saved in Bundler's global config for future `bundle gem`
|
||||
use.
|
||||
|
||||
* `--no-ci`:
|
||||
Do not use a continuous integration service (overrides `--ci` specified in
|
||||
the global config).
|
||||
|
||||
* `--linter`, `--linter=rubocop`, `--linter=standard`:
|
||||
Specify the linter and code formatter that Bundler should add to the
|
||||
project's development dependencies. Acceptable values are `rubocop` and
|
||||
`standard`. A configuration file will be generated in the project directory.
|
||||
Given no option is specified:
|
||||
|
||||
When Bundler is configured to add a linter, this defaults to Bundler's
|
||||
global config setting `gem.linter`.
|
||||
|
||||
When Bundler is configured not to add a linter, an interactive prompt
|
||||
will be displayed and the answer will be used for the current rubygem project.
|
||||
|
||||
When Bundler is unconfigured, an interactive prompt will be displayed and
|
||||
the answer will be saved in Bundler's global config for future `bundle gem`
|
||||
use.
|
||||
|
||||
* `--no-linter`:
|
||||
Do not add a linter (overrides `--linter` specified in the global config).
|
||||
|
||||
* `--edit=EDIT`, `-e=EDIT`:
|
||||
Open the resulting GEM_NAME.gemspec in EDIT, or the default editor if not
|
||||
specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`.
|
||||
|
||||
* `--bundle`:
|
||||
Run `bundle install` after creating the gem.
|
||||
|
||||
* `--no-bundle`:
|
||||
Do not run `bundle install` after creating the gem.
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
* [bundle config(1)](bundle-config.1.html)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user