summaryrefslogtreecommitdiff
path: root/lib/bundler/settings.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/settings.rb')
-rw-r--r--lib/bundler/settings.rb587
1 files changed, 587 insertions, 0 deletions
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
new file mode 100644
index 0000000000..fd77c2f7fc
--- /dev/null
+++ b/lib/bundler/settings.rb
@@ -0,0 +1,587 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Settings
+ autoload :Mirror, File.expand_path("mirror", __dir__)
+ autoload :Mirrors, File.expand_path("mirror", __dir__)
+ autoload :Validator, File.expand_path("settings/validator", __dir__)
+
+ BOOL_KEYS = %w[
+ auto_install
+ cache_all
+ cache_all_platforms
+ clean
+ deployment
+ disable_checksum_validation
+ disable_exec_load
+ disable_local_branch_check
+ disable_local_revision_check
+ disable_shared_gems
+ disable_version_check
+ force_ruby_platform
+ frozen
+ gem.changelog
+ gem.coc
+ gem.mit
+ gem.bundle
+ git.allow_insecure
+ global_gem_cache
+ ignore_messages
+ init_gems_rb
+ inline
+ lockfile_checksums
+ no_build_extension
+ no_install
+ no_install_plugin
+ no_prune
+ path.system
+ plugins
+ prefer_patch
+ silence_deprecations
+ silence_root_warning
+ update_requires_all_flag
+ verbose
+ ].freeze
+
+ NUMBER_KEYS = %w[
+ cooldown
+ jobs
+ redirect
+ retry
+ ssl_verify_mode
+ timeout
+ ].freeze
+
+ ARRAY_KEYS = %w[
+ only
+ with
+ without
+ ].freeze
+
+ STRING_KEYS = %w[
+ bin
+ cache_path
+ console
+ default_cli_command
+ gem.ci
+ gem.github_username
+ gem.linter
+ gem.rubocop
+ gem.test
+ gemfile
+ lockfile
+ path
+ shebang
+ simulate_version
+ system_bindir
+ trust-policy
+ version
+ ].freeze
+
+ DEFAULT_CONFIG = {
+ "BUNDLE_SILENCE_DEPRECATIONS" => false,
+ "BUNDLE_DISABLE_VERSION_CHECK" => true,
+ "BUNDLE_PREFER_PATCH" => false,
+ "BUNDLE_REDIRECT" => 5,
+ "BUNDLE_RETRY" => 3,
+ "BUNDLE_TIMEOUT" => 10,
+ "BUNDLE_VERSION" => "lockfile",
+ "BUNDLE_LOCKFILE_CHECKSUMS" => true,
+ "BUNDLE_CACHE_ALL" => true,
+ "BUNDLE_PLUGINS" => true,
+ "BUNDLE_GLOBAL_GEM_CACHE" => false,
+ "BUNDLE_UPDATE_REQUIRES_ALL_FLAG" => false,
+ }.freeze
+
+ def initialize(root = nil)
+ @root = root
+ @local_config = load_config(local_config_file)
+ @local_root = root || Pathname.new(".bundle").expand_path
+
+ @env_config = ENV.to_h
+ @env_config.select! {|key, _value| key.start_with?("BUNDLE_") }
+ @env_config.delete("BUNDLE_")
+
+ @global_config = load_config(global_config_file)
+ @temporary = {}
+
+ @key_cache = {}
+ end
+
+ def [](name)
+ key = key_for(name)
+
+ value = nil
+ configs.each do |_, config|
+ value = config[key]
+ next if value.nil?
+ break
+ end
+
+ converted_value(value, name)
+ end
+
+ def set_command_option(key, value)
+ temporary(key => value)
+ value
+ end
+
+ def set_command_option_if_given(key, value)
+ return if value.nil?
+ set_command_option(key, value)
+ end
+
+ def set_local(key, value)
+ local_config_file = @local_root.join("config")
+
+ set_key(key, value, @local_config, local_config_file)
+ end
+
+ def temporary(update)
+ existing = Hash[update.map {|k, _| [k, @temporary[key_for(k)]] }]
+ update.each do |k, v|
+ set_key(k, v, @temporary, nil)
+ end
+ return unless block_given?
+ begin
+ yield
+ ensure
+ existing.each {|k, v| set_key(k, v, @temporary, nil) }
+ end
+ end
+
+ def set_global(key, value)
+ set_key(key, value, @global_config, global_config_file)
+ end
+
+ def all
+ keys = @temporary.keys.union(@global_config.keys, @local_config.keys, @env_config.keys)
+
+ keys.map! do |key|
+ key = key.delete_prefix("BUNDLE_")
+ key.gsub!("___", "-")
+ key.gsub!("__", ".")
+ key.downcase!
+ key
+ end.sort!
+ keys
+ end
+
+ def local_overrides
+ repos = {}
+ all.each do |k|
+ repos[k.delete_prefix("local.")] = self[k] if k.start_with?("local.")
+ end
+ repos
+ end
+
+ def mirror_for(uri)
+ if uri.is_a?(String)
+ require_relative "vendored_uri"
+ uri = Gem::URI(uri)
+ end
+
+ gem_mirrors.for(uri.to_s).uri
+ end
+
+ def credentials_for(uri)
+ self[uri.to_s] || self[uri.host]
+ end
+
+ def gem_mirrors
+ all.inject(Mirrors.new) do |mirrors, k|
+ mirrors.parse(k, self[k]) if k.start_with?("mirror.")
+ mirrors
+ end
+ end
+
+ def locations(key)
+ key = key_for(key)
+ configs.keys.inject({}) do |partial_locations, level|
+ value_on_level = configs[level][key]
+ partial_locations[level] = value_on_level unless value_on_level.nil?
+ partial_locations
+ end
+ end
+
+ def pretty_values_for(exposed_key)
+ key = key_for(exposed_key)
+
+ locations = []
+
+ if value = @temporary[key]
+ locations << "Set for the current command: #{printable_value(value, exposed_key).inspect}"
+ end
+
+ if value = @local_config[key]
+ locations << "Set for your local app (#{local_config_file}): #{printable_value(value, exposed_key).inspect}"
+ end
+
+ if value = @env_config[key]
+ locations << "Set via #{key}: #{printable_value(value, exposed_key).inspect}"
+ end
+
+ if value = @global_config[key]
+ locations << "Set for the current user (#{global_config_file}): #{printable_value(value, exposed_key).inspect}"
+ end
+
+ return ["You have not configured a value for `#{exposed_key}`"] if locations.empty?
+ locations
+ end
+
+ def processor_count
+ require "etc"
+ Etc.nprocessors
+ rescue StandardError
+ 1
+ end
+
+ # for legacy reasons, in Bundler 2, we do not respect :disable_shared_gems
+ def path
+ configs.each do |_level, settings|
+ path = value_for("path", settings)
+ path_system = value_for("path.system", settings)
+ disabled_shared_gems = value_for("disable_shared_gems", settings)
+ next if path.nil? && path_system.nil? && disabled_shared_gems.nil?
+ system_path = path_system || (disabled_shared_gems == false)
+ return Path.new(path, system_path)
+ end
+
+ path = "vendor/bundle" if self[:deployment]
+
+ Path.new(path, false)
+ end
+
+ Path = Struct.new(:explicit_path, :system_path) do
+ def path
+ path = base_path
+ path = File.join(path, Bundler.ruby_scope) unless use_system_gems?
+ path
+ end
+
+ def use_system_gems?
+ return true if system_path
+ return false if explicit_path
+ !Bundler.feature_flag.bundler_5_mode?
+ end
+
+ def base_path
+ path = explicit_path
+ path ||= ".bundle" unless use_system_gems?
+ path ||= Bundler.rubygems.gem_dir
+ path
+ end
+
+ def base_path_relative_to_pwd
+ base_path = Pathname.new(self.base_path)
+ expanded_base_path = base_path.expand_path(Bundler.root)
+ relative_path = expanded_base_path.relative_path_from(Pathname.pwd)
+ if relative_path.to_s.start_with?("..")
+ relative_path = base_path if base_path.absolute?
+ else
+ relative_path = Pathname.new(File.join(".", relative_path))
+ end
+ relative_path
+ rescue ArgumentError
+ expanded_base_path
+ end
+
+ def validate!
+ return unless explicit_path && system_path
+ path = Bundler.settings.pretty_values_for(:path)
+ path.unshift(nil, "path:") unless path.empty?
+ system_path = Bundler.settings.pretty_values_for("path.system")
+ system_path.unshift(nil, "path.system:") unless system_path.empty?
+ disable_shared_gems = Bundler.settings.pretty_values_for(:disable_shared_gems)
+ disable_shared_gems.unshift(nil, "disable_shared_gems:") unless disable_shared_gems.empty?
+ raise InvalidOption,
+ "Using a custom path while using system gems is unsupported.\n#{path.join("\n")}\n#{system_path.join("\n")}\n#{disable_shared_gems.join("\n")}"
+ end
+ end
+
+ def ignore_config?
+ ENV["BUNDLE_IGNORE_CONFIG"]
+ end
+
+ def app_cache_path
+ @app_cache_path ||= self[:cache_path] || "vendor/cache"
+ end
+
+ def installation_parallelization
+ self[:jobs] || processor_count
+ end
+
+ def validate!
+ all.each do |raw_key|
+ [@local_config, @env_config, @global_config].each do |settings|
+ value = value_for(raw_key, settings)
+ Validator.validate!(raw_key, value, settings.dup)
+ end
+ end
+ end
+
+ def key_for(key)
+ @key_cache[key] ||= self.class.key_for(key)
+ end
+
+ private
+
+ def configs
+ @configs ||= {
+ temporary: @temporary,
+ local: @local_config,
+ env: @env_config,
+ global: @global_config,
+ default: DEFAULT_CONFIG,
+ }
+ end
+
+ def value_for(name, config)
+ converted_value(config[key_for(name)], name)
+ end
+
+ def parent_setting_for(name)
+ split_specific_setting_for(name)[0]
+ end
+
+ def specific_gem_for(name)
+ split_specific_setting_for(name)[1]
+ end
+
+ def split_specific_setting_for(name)
+ name.split(".")
+ end
+
+ def is_bool(name)
+ name = self.class.key_to_s(name)
+ BOOL_KEYS.include?(name) || BOOL_KEYS.include?(parent_setting_for(name))
+ end
+
+ def is_string(name)
+ name = self.class.key_to_s(name)
+ STRING_KEYS.include?(name) || name.start_with?("local.") || name.start_with?("mirror.") || name.start_with?("build.")
+ end
+
+ def to_bool(value)
+ case value
+ when String
+ value.match?(/\A(false|f|no|n|0|)\z/i) ? false : true
+ when nil, false
+ false
+ else
+ true
+ end
+ end
+
+ def is_num(key)
+ NUMBER_KEYS.include?(self.class.key_to_s(key))
+ end
+
+ def is_array(key)
+ ARRAY_KEYS.include?(self.class.key_to_s(key))
+ end
+
+ def is_credential(key)
+ key == "gem.push_key"
+ end
+
+ def is_userinfo(value)
+ value.include?(":")
+ end
+
+ def to_array(value)
+ return [] unless value
+ value.tr(" ", ":").split(":").map(&:to_sym)
+ end
+
+ def array_to_s(array)
+ array = Array(array)
+ return nil if array.empty?
+ array.join(":").tr(" ", ":")
+ end
+
+ def set_key(raw_key, value, hash, file)
+ raw_key = self.class.key_to_s(raw_key)
+ value = array_to_s(value) if is_array(raw_key)
+
+ key = key_for(raw_key)
+
+ return if hash[key] == value
+
+ hash[key] = value
+ hash.delete(key) if value.nil?
+
+ Validator.validate!(raw_key, converted_value(value, raw_key), hash)
+
+ return unless file
+
+ SharedHelpers.filesystem_access(file.dirname, :create) do |p|
+ FileUtils.mkdir_p(p)
+ end
+
+ SharedHelpers.filesystem_access(file) do |p|
+ p.open("w") {|f| f.write(serializer_class.dump(hash)) }
+ end
+ end
+
+ def converted_value(value, key)
+ key = self.class.key_to_s(key)
+
+ if is_array(key)
+ to_array(value)
+ elsif value.nil?
+ nil
+ elsif is_bool(key) || value == "false"
+ to_bool(value)
+ elsif is_num(key)
+ value.to_i
+ else
+ value.to_s
+ end
+ end
+
+ def printable_value(value, key)
+ converted = converted_value(value, key)
+ return converted unless converted.is_a?(String)
+
+ if is_string(key)
+ converted
+ elsif is_credential(key)
+ "[REDACTED]"
+ elsif is_userinfo(converted)
+ username, pass = converted.split(":", 2)
+
+ if pass == "x-oauth-basic"
+ username = "[REDACTED]"
+ else
+ pass = "[REDACTED]"
+ end
+
+ [username, pass].join(":")
+ else
+ converted
+ end
+ end
+
+ def global_config_file
+ if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty?
+ Pathname.new(ENV["BUNDLE_CONFIG"])
+ elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty?
+ Pathname.new(ENV["BUNDLE_USER_CONFIG"])
+ elsif ENV["BUNDLE_USER_HOME"] && !ENV["BUNDLE_USER_HOME"].empty?
+ Pathname.new(ENV["BUNDLE_USER_HOME"]).join("config")
+ elsif Bundler.rubygems.user_home && !Bundler.rubygems.user_home.empty?
+ Pathname.new(Bundler.rubygems.user_home).join(".bundle/config")
+ end
+ end
+
+ def local_config_file
+ Pathname.new(@root).join("config") if @root
+ end
+
+ def load_config(config_file)
+ return {} if !config_file || ignore_config?
+ SharedHelpers.filesystem_access(config_file, :read) do |file|
+ valid_file = file.exist? && !file.size.zero?
+ return {} unless valid_file
+ (serializer_class.load(file.read) || {}).inject({}) do |config, (k, v)|
+ k = k.dup
+ k << "/" if /https?:/i.match?(k) && !k.end_with?("/", "__#{FALLBACK_TIMEOUT_URI_OPTION.upcase}")
+ k.gsub!(".", "__")
+
+ unless k.start_with?("#")
+ if k.include?("-")
+ Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \
+ "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \
+ "Please edit #{file} and replace any dashes in configuration keys with a triple underscore (`___`)."
+
+ # string hash keys are frozen
+ k = k.gsub("-", "___")
+ end
+
+ config[k] = v
+ end
+
+ config
+ end
+ end
+ end
+
+ def serializer_class
+ require "rubygems/yaml_serializer"
+ Gem::YAMLSerializer
+ rescue LoadError
+ # TODO: Remove this when RubyGems 3.4 is EOL
+ require_relative "yaml_serializer"
+ YAMLSerializer
+ end
+
+ FALLBACK_TIMEOUT_URI_OPTION = "fallback_timeout"
+
+ NORMALIZE_URI_OPTIONS_PATTERN =
+ /
+ \A
+ (\w+\.)? # optional prefix key
+ (https?.*?) # URI
+ (\.#{FALLBACK_TIMEOUT_URI_OPTION})? # optional suffix key
+ \z
+ /ix
+
+ def self.key_for(key)
+ key = key_to_s(key)
+ key = normalize_uri(key) if key.start_with?("http", "mirror.http")
+ key = key.gsub(".", "__")
+ key.gsub!("-", "___")
+ key.upcase!
+
+ key.gsub(/\A([ #]*)/, '\1BUNDLE_')
+ end
+
+ # TODO: duplicates Rubygems#normalize_uri
+ # TODO: is this the correct place to validate mirror URIs?
+ def self.normalize_uri(uri)
+ uri = uri.to_s
+ if uri =~ NORMALIZE_URI_OPTIONS_PATTERN
+ prefix = $1
+ uri = $2
+ suffix = $3
+ end
+ uri = URINormalizer.normalize_suffix(uri)
+ require_relative "vendored_uri"
+ uri = Gem::URI(uri)
+ unless uri.absolute?
+ raise ArgumentError, format("Gem sources must be absolute. You provided '%s'.", uri)
+ end
+ "#{prefix}#{uri}#{suffix}"
+ end
+
+ # This is a hot method, so avoid respond_to? checks on every invocation
+ if :read.respond_to?(:name)
+ def self.key_to_s(key)
+ case key
+ when String
+ key
+ when Symbol
+ key.name
+ when Gem::URI::HTTP
+ key.to_s
+ else
+ raise ArgumentError, "Invalid key: #{key.inspect}"
+ end
+ end
+ else
+ def self.key_to_s(key)
+ case key
+ when String
+ key
+ when Symbol
+ key.to_s
+ when Gem::URI::HTTP
+ key.to_s
+ else
+ raise ArgumentError, "Invalid key: #{key.inspect}"
+ end
+ end
+ end
+ end
+end