bundler-ignore-dependency 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a572dd95bfbcf340640e90c3b98f408959bf7e60d86c0e83be7c457318887001
4
+ data.tar.gz: 8d0cc03de9e2246632ebaec86248f08944ae8a9fb6e17eb805a9eef6ad6a1e82
5
+ SHA512:
6
+ metadata.gz: e34990614cfe7085aa0f70b917841f12dcaa547bf1b05d5ccafb6244c57a673cdf24c59fed249049ecbb198fbef3ed1a0572b7efe7e7b9255ed5a44375a4d6dc
7
+ data.tar.gz: 5d3d5c5a9655bda04e900f9a7086562a89ed2b1ae0edbb12d29de67a1ef850dc6d21b6b396310fd5a738aee8a3ea9338376434b6ee5dabf55ef40937358b7f44
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/definition"
4
+
5
+ module Bundler
6
+ module IgnoreDependency
7
+ # Adds ignored_dependencies attribute to Bundler::Definition
8
+ #
9
+ # Purpose: Stores the set of dependencies that should be ignored during
10
+ # dependency resolution. This attribute is populated by DSLPatch from the
11
+ # Gemfile's ignore_dependency! directives and is accessed by all other patches
12
+ # to determine which dependencies to filter.
13
+ #
14
+ # The attribute acts as the central source of truth for ignored dependencies
15
+ # throughout the entire dependency resolution and materialization process.
16
+ module DefinitionPatch
17
+ def ignored_dependencies
18
+ @ignored_dependencies ||= {}
19
+ end
20
+
21
+ attr_writer :ignored_dependencies
22
+ end
23
+ end
24
+
25
+ Definition.prepend(IgnoreDependency::DefinitionPatch)
26
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/dsl"
4
+
5
+ module Bundler
6
+ module IgnoreDependency
7
+ # Adds ignore_dependency! method to Bundler::Dsl (Gemfile DSL)
8
+ #
9
+ # Purpose: Provides the user-facing API for specifying which dependencies
10
+ # should be ignored. Users call ignore_dependency! in their Gemfile to mark
11
+ # dependencies (Ruby, RubyGems, or gems) that should be filtered out of
12
+ # dependency resolution and version constraint checking.
13
+ #
14
+ # When ignore_dependency! is called, it normalizes the dependency names
15
+ # (e.g., :ruby -> "Ruby\0") and stores them in @ignored_dependencies.
16
+ # These settings are then transferred to the Definition object via to_definition().
17
+ module DslPatch
18
+ def initialize
19
+ super
20
+ @ignored_dependencies = {}
21
+ end
22
+
23
+ def ignore_dependency!(name, type: :complete)
24
+ unless [:complete, :upper].include?(type)
25
+ raise ArgumentError, "type must be :complete or :upper, got #{type.inspect}"
26
+ end
27
+
28
+ key = normalize_dependency_name(name)
29
+ ignored_dependencies[key] = type
30
+ end
31
+
32
+ def to_definition(lockfile, unlock)
33
+ definition = super
34
+ definition.ignored_dependencies = ignored_dependencies.dup
35
+ definition
36
+ end
37
+
38
+ def ignored_dependencies
39
+ @ignored_dependencies ||= {}
40
+ end
41
+
42
+ private
43
+
44
+ def normalize_dependency_name(name)
45
+ case name
46
+ when :ruby
47
+ IgnoreDependency::RUBY_DEPENDENCY_NAME
48
+ when :rubygems
49
+ IgnoreDependency::RUBYGEMS_DEPENDENCY_NAME
50
+ when String
51
+ name
52
+ else
53
+ raise ArgumentError, "dependency name must be :ruby, :rubygems, or a gem name string"
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ Dsl.prepend(IgnoreDependency::DslPatch)
60
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module IgnoreDependency
5
+ # Removes completely ignored gems from resolved gem specifications
6
+ #
7
+ # Purpose: After dependency resolution, LazySpecification objects are created
8
+ # from resolved specs. This patch intercepts that creation to remove completely
9
+ # ignored gems from the dependencies list.
10
+ #
11
+ # Critical for: Preventing ignored gems from appearing in the lockfile
12
+ # Without this patch, completely ignored gems would still appear in Gemfile.lock
13
+ # even though they're not actually being resolved.
14
+ #
15
+ # Flow: ResolverPatch filters during resolution → LazySpecificationPatch filters
16
+ # the resolved specs → MaterializationPatch prevents fetching
17
+ module LazySpecificationPatch
18
+ def from_spec(s)
19
+ filter_ignored_dependencies(super)
20
+ end
21
+
22
+ private
23
+
24
+ def filter_ignored_dependencies(lazy_spec)
25
+ ignored_names = IgnoreDependency.completely_ignored_gem_names
26
+
27
+ if ignored_names.any?
28
+ lazy_spec.dependencies.reject! do |dep|
29
+ ignored_names.include?(dep.name)
30
+ end
31
+ end
32
+
33
+ lazy_spec
34
+ end
35
+ end
36
+ end
37
+
38
+ LazySpecification.singleton_class.prepend(IgnoreDependency::LazySpecificationPatch)
39
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/match_metadata"
4
+
5
+ module Bundler
6
+ module IgnoreDependency
7
+ # Overrides Ruby and RubyGems version matching checks
8
+ #
9
+ # Purpose: Allows gems with restrictive Ruby/RubyGems version requirements
10
+ # to be installed even when the current environment doesn't meet those constraints.
11
+ #
12
+ # Behaviors:
13
+ # - When Ruby is completely ignored: accepts gems regardless of required_ruby_version
14
+ # - When Ruby upper bound is ignored: removes upper bounds from requirements,
15
+ # allowing newer Ruby versions (e.g., Ruby 4.0 can install gems marked as < 4.0)
16
+ # - Same logic applies to RubyGems version matching
17
+ #
18
+ # Example: A gem with required_ruby_version = [">= 2.7", "< 3.0"] can be installed
19
+ # on Ruby 3.2 when ignore_dependency! :ruby, type: :upper is used.
20
+ module MatchMetadataPatch
21
+ def matches_current_ruby?
22
+ matches_version?(:ruby, Gem.ruby_version, @required_ruby_version)
23
+ end
24
+
25
+ def matches_current_rubygems?
26
+ matches_version?(:rubygems, Gem.rubygems_version, @required_rubygems_version)
27
+ end
28
+
29
+ private
30
+
31
+ def matches_version?(dependency_type, current_version, required_version)
32
+ return true if IgnoreDependency.send("#{dependency_type}_completely_ignored?")
33
+
34
+ requirement = if IgnoreDependency.send("#{dependency_type}_upper_bound_ignored?")
35
+ IgnoreDependency.remove_upper_bounds(required_version)
36
+ else
37
+ required_version
38
+ end
39
+
40
+ requirement.satisfied_by?(current_version)
41
+ end
42
+ end
43
+ end
44
+
45
+ MatchMetadata.prepend(IgnoreDependency::MatchMetadataPatch)
46
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/materialization"
4
+
5
+ module Bundler
6
+ module IgnoreDependency
7
+ # Prevents fetching/installing of completely ignored gems
8
+ #
9
+ # Purpose: During the materialization phase, Bundler prepares to actually
10
+ # install/download gems. This patch removes completely ignored gems from
11
+ # the list of dependencies to materialize.
12
+ #
13
+ # Critical for: Preventing unnecessary gem downloads and avoiding network
14
+ # errors when the server doesn't have compatible versions of ignored gems.
15
+ #
16
+ # Without this patch:
17
+ # - Bundler would try to fetch completely ignored gems from rubygems.org
18
+ # - If the gem doesn't exist or has no compatible platform, installation fails
19
+ # - Network bandwidth is wasted downloading gems that won't be used
20
+ #
21
+ # Flow: ResolverPatch filters during resolution → LazySpecificationPatch
22
+ # filters specs → MaterializationPatch prevents fetching
23
+ module MaterializationPatch
24
+ def dependencies
25
+ deps = super
26
+ ignored_names = IgnoreDependency.completely_ignored_gem_names
27
+
28
+ return deps if ignored_names.empty?
29
+
30
+ deps.reject { |dep, _| ignored_names.include?(dep.name) }
31
+ end
32
+ end
33
+ end
34
+
35
+ Materialization.prepend(IgnoreDependency::MaterializationPatch)
36
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/resolver"
4
+
5
+ module Bundler
6
+ module IgnoreDependency
7
+ # Filters dependencies during Bundler's resolution phase
8
+ #
9
+ # Purpose: This is the core filtering mechanism. During dependency resolution,
10
+ # it removes or modifies dependencies based on ignore rules:
11
+ # - Completely ignored dependencies are removed entirely (not part of resolution)
12
+ # - Upper bound ignored dependencies have their upper bounds removed (e.g., < 3.0)
13
+ # while keeping lower bounds (e.g., >= 2.7)
14
+ #
15
+ # This ensures that:
16
+ # 1. Completely ignored gems don't participate in dependency resolution at all
17
+ # 2. Upper bound ignored constraints don't prevent newer versions from being resolved
18
+ # 3. Lower bounds are still respected (e.g., minimum version requirements)
19
+ #
20
+ # This filtering happens before the resolver sees dependencies, making it the
21
+ # primary mechanism for excluding dependencies from the resolution process.
22
+ module ResolverPatch
23
+ private
24
+
25
+ def to_dependency_hash(dependencies, packages)
26
+ super(filter_ignored_dependencies(dependencies), packages)
27
+ end
28
+
29
+ def filter_ignored_dependencies(deps)
30
+ ignored = IgnoreDependency.ignored_dependencies
31
+ return deps if ignored.empty?
32
+
33
+ deps.filter_map do |dep|
34
+ filter_dependency(dep, ignored)
35
+ end
36
+ end
37
+
38
+ def filter_dependency(dep, ignored)
39
+ case ignored[dep.name]
40
+ when :complete
41
+ nil # Remove the dependency entirely
42
+ when :upper
43
+ apply_upper_bound_filter(dep)
44
+ else
45
+ dep
46
+ end
47
+ end
48
+
49
+ def apply_upper_bound_filter(dep)
50
+ filtered = IgnoreDependency.remove_upper_bounds(dep.requirement)
51
+ Gem::Dependency.new(dep.name, filtered)
52
+ end
53
+ end
54
+ end
55
+
56
+ Resolver.prepend(IgnoreDependency::ResolverPatch)
57
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/shared_helpers"
4
+
5
+ module Bundler
6
+ module IgnoreDependency
7
+ # Skips API validation for completely ignored gem dependencies
8
+ #
9
+ # Purpose: When Bundler fetches gem metadata from rubygems.org or other
10
+ # sources, it validates that the dependencies reported by the server match
11
+ # what was previously resolved. This validation raises an error if there's
12
+ # a mismatch (e.g., when a gem's dependencies change between versions).
13
+ #
14
+ # For completely ignored gems, we filter them out before validation because:
15
+ # 1. They won't be installed anyway
16
+ # 2. We don't care about their dependency structure
17
+ # 3. Differences in their deps shouldn't cause validation errors
18
+ #
19
+ # Without this patch, if a completely ignored gem has different dependencies
20
+ # in the API than before, bundle install would fail with APIResponseMismatchError.
21
+ module SharedHelpersPatch
22
+ def ensure_same_dependencies(spec, old_deps, new_deps)
23
+ super(spec, old_deps, IgnoreDependency.filter_ignored_gem_dependencies(new_deps))
24
+ end
25
+ end
26
+ end
27
+
28
+ SharedHelpers.singleton_class.prepend(IgnoreDependency::SharedHelpersPatch)
29
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module IgnoreDependency
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ignore_dependency/version"
4
+
5
+ module Bundler
6
+ module IgnoreDependency
7
+ LOWER_BOUND_OPERATORS = [">=", ">", "="].freeze
8
+ RUBY_DEPENDENCY_NAME = "Ruby\0"
9
+ RUBYGEMS_DEPENDENCY_NAME = "RubyGems\0"
10
+
11
+ class << self
12
+ def ignored_dependencies
13
+ Bundler.definition&.ignored_dependencies || {}
14
+ end
15
+
16
+ def completely_ignored_gem_names
17
+ @completely_ignored_gem_names ||= ignored_dependencies.filter_map do |name, type|
18
+ # Filter out Ruby/RubyGems internal names (they contain \0)
19
+ name if type == :complete && !name.include?("\0")
20
+ end.to_set
21
+ end
22
+
23
+ def ignore_type_for(name)
24
+ ignored_dependencies[name]
25
+ end
26
+
27
+ def completely_ignored?(name)
28
+ ignore_type_for(name) == :complete
29
+ end
30
+
31
+ def upper_bound_ignored?(name)
32
+ ignore_type_for(name) == :upper
33
+ end
34
+
35
+ def ruby_completely_ignored?
36
+ completely_ignored?(RUBY_DEPENDENCY_NAME)
37
+ end
38
+
39
+ def ruby_upper_bound_ignored?
40
+ upper_bound_ignored?(RUBY_DEPENDENCY_NAME)
41
+ end
42
+
43
+ def rubygems_completely_ignored?
44
+ completely_ignored?(RUBYGEMS_DEPENDENCY_NAME)
45
+ end
46
+
47
+ def rubygems_upper_bound_ignored?
48
+ upper_bound_ignored?(RUBYGEMS_DEPENDENCY_NAME)
49
+ end
50
+
51
+ def remove_upper_bounds(requirement)
52
+ return Gem::Requirement.default if requirement.nil? || requirement.none?
53
+
54
+ lower_bounds = requirement.requirements.filter_map do |op, version|
55
+ if LOWER_BOUND_OPERATORS.include?(op)
56
+ [op, version]
57
+ elsif op == "~>"
58
+ # Pessimistic operator ~> X.Y means >= X.Y AND < X+1.0
59
+ # We keep only the lower bound part
60
+ [">=", version]
61
+ end
62
+ # Skip < and <= operators (upper bounds)
63
+ end
64
+
65
+ return Gem::Requirement.default if lower_bounds.empty?
66
+
67
+ Gem::Requirement.new(lower_bounds.map { |op, v| "#{op} #{v}" })
68
+ end
69
+
70
+ def apply_ignore_rule(requirement, name)
71
+ return Gem::Requirement.default if completely_ignored?(name)
72
+ return remove_upper_bounds(requirement) if upper_bound_ignored?(name)
73
+
74
+ requirement
75
+ end
76
+
77
+ def filter_ignored_gem_dependencies(dependencies)
78
+ ignored_names = completely_ignored_gem_names
79
+ return dependencies if ignored_names.empty?
80
+
81
+ dependencies.reject { |dep| ignored_names.include?(dep.name) }
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ require_relative "ignore_dependency/match_metadata_patch"
88
+ require_relative "ignore_dependency/dsl_patch"
89
+ require_relative "ignore_dependency/definition_patch"
90
+ require_relative "ignore_dependency/resolver_patch"
91
+ require_relative "ignore_dependency/lazy_specification_patch"
92
+ require_relative "ignore_dependency/shared_helpers_patch"
93
+ require_relative "ignore_dependency/materialization_patch"
data/plugins.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/bundler/ignore_dependency"
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bundler-ignore-dependency
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ufuk Kayserilioglu
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A Bundler plugin that adds an ignore_dependency! DSL method to allow
13
+ ignoring version constraints on Ruby, RubyGems, or gem dependencies.
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - lib/bundler/ignore_dependency.rb
19
+ - lib/bundler/ignore_dependency/definition_patch.rb
20
+ - lib/bundler/ignore_dependency/dsl_patch.rb
21
+ - lib/bundler/ignore_dependency/lazy_specification_patch.rb
22
+ - lib/bundler/ignore_dependency/match_metadata_patch.rb
23
+ - lib/bundler/ignore_dependency/materialization_patch.rb
24
+ - lib/bundler/ignore_dependency/resolver_patch.rb
25
+ - lib/bundler/ignore_dependency/shared_helpers_patch.rb
26
+ - lib/bundler/ignore_dependency/version.rb
27
+ - plugins.rb
28
+ homepage: https://github.com/paracycle/bundler-ignore-dependency
29
+ licenses:
30
+ - MIT
31
+ metadata:
32
+ allowed_push_host: https://rubygems.org
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 4.0.3
48
+ specification_version: 4
49
+ summary: Bundler plugin to ignore dependency version constraints
50
+ test_files: []