Class: Licensed::Source::Bundler

Inherits:
Object
  • Object
show all
Defined in:
lib/licensed/source/bundler.rb

Constant Summary collapse

GEMFILES =
%w{Gemfile gems.rb}.freeze
DEFAULT_WITHOUT_GROUPS =
i{development test}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Bundler

Returns a new instance of Bundler.



17
18
19
# File 'lib/licensed/source/bundler.rb', line 17

def initialize(config)
  @config = config
end

Class Method Details

.typeObject



13
14
15
# File 'lib/licensed/source/bundler.rb', line 13

def self.type
  "rubygem"
end

Instance Method Details

#bundler_specObject

Returns a gemspec for bundler, found and loaded by running ‘gem specification bundler` This is a hack to work around bundler not placing it’s own spec at ‘::Bundler.specs_path` when it’s an explicit dependency



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/licensed/source/bundler.rb', line 142

def bundler_spec
  # cache this so we run CLI commands as few times as possible
  return @bundler_spec if defined?(@bundler_spec)

  # finding the bundler gem is dependent on having `gem` available
  unless Licensed::Shell.tool_available?("gem")
    @bundler_spec = nil
    return
  end

  # Bundler is always used from the default gem install location.
  # we can use `gem specification bundler` with a clean ENV to
  # get the system bundler gem as YAML
  yaml = ::Bundler.with_original_env { Licensed::Shell.execute("gem", "specification", "bundler") }
  @bundler_spec = Gem::Specification.from_yaml(yaml)
end

#definitionObject

Build the bundler definition



160
161
162
# File 'lib/licensed/source/bundler.rb', line 160

def definition
  @definition ||= ::Bundler::Definition.build(gemfile_path, lockfile_path, nil)
end

#dependenciesObject



25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/licensed/source/bundler.rb', line 25

def dependencies
  @dependencies ||= with_local_configuration do
    specs.map do |spec|
      Licensed::Dependency.new(spec.gem_dir, {
        "type"     => Bundler.type,
        "name"     => spec.name,
        "version"  => spec.version.to_s,
        "summary"  => spec.summary,
        "homepage" => spec.homepage
      })
    end
  end
end

#enabled?Boolean

Returns:

  • (Boolean)


21
22
23
# File 'lib/licensed/source/bundler.rb', line 21

def enabled?
  defined?(::Bundler) && lockfile_path && lockfile_path.exist?
end

#exclude_development_dependencies?Boolean

Returns whether development dependencies should be excluded

Returns:

  • (Boolean)


130
131
132
133
134
135
136
137
# File 'lib/licensed/source/bundler.rb', line 130

def exclude_development_dependencies?
  @include_development ||= begin
    # check whether the development dependency group is explicitly removed
    # or added via bundler and licensed configurations
    groups = [:development] - Array(::Bundler.settings[:without]) + Array(::Bundler.settings[:with]) - exclude_groups
    !groups.include?(:development)
  end
end

#exclude_groupsObject

Returns any groups to exclude specified from both licensed configuration and bundler configuration. Defaults to [:development, :test] + ::Bundler.settings



173
174
175
176
177
178
179
# File 'lib/licensed/source/bundler.rb', line 173

def exclude_groups
  @exclude_groups ||= begin
    exclude = Array(@config.dig("rubygems", "without"))
    exclude.push(*DEFAULT_WITHOUT_GROUPS) if exclude.empty?
    exclude.uniq.map(&:to_sym)
  end
end

#gem_spec(dependency) ⇒ Object

Returns a Gem::Specification for the provided gem argument. If a Gem::Specification isn’t found, an error will be raised.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/licensed/source/bundler.rb', line 80

def gem_spec(dependency)
  return unless dependency

  # bundler specifications aren't put in ::Bundler.specs_path, even if the
  # gem is a runtime dependency.  it needs to be handled specially
  return bundler_spec if dependency.name == "bundler"

  # find a specifiction from the resolved ::Bundler::Definition specs
  spec = definition.resolve.find { |s| s.satisfies?(dependency) }
  return spec unless spec.is_a?(::Bundler::LazySpecification)

  # if the specification is coming from a gemspec source,
  # we can get a non-lazy specification straight from the source
  if spec.source.is_a?(::Bundler::Source::Gemspec) || spec.source.is_a?(::Bundler::Source::Path)
    return spec.source.specs.first
  end

  # spec.source.specs gives access to specifications with more
  # information than spec itself, including platform-specific gems.
  # try to find a specification that matches `spec`
  if source_spec = spec.source.specs.find { |s| s.name == spec.name && s.version == spec.version }
    spec = source_spec
  end

  # look for a specification at the bundler specs path
  spec_path = ::Bundler.specs_path.join("#{spec.full_name}.gemspec")
  return unless File.exist?(spec_path.to_s)
  Gem::Specification.load(spec_path.to_s)
end

#gemfile_pathObject

Returns the path to the Bundler Gemfile



182
183
184
185
# File 'lib/licensed/source/bundler.rb', line 182

def gemfile_path
  @gemfile_path ||= GEMFILES.map { |g| @config.pwd.join g }
                            .find { |f| f.exist? }
end

#groupsObject

Returns the bundle definition groups, removing “without” groups, and including “with” groups



166
167
168
# File 'lib/licensed/source/bundler.rb', line 166

def groups
  definition.groups - Array(::Bundler.settings[:without]) + Array(::Bundler.settings[:with]) - exclude_groups
end

#include?(dependency, source) ⇒ Boolean

Returns whether a dependency should be included in the final

Returns:

  • (Boolean)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/licensed/source/bundler.rb', line 111

def include?(dependency, source)
  # ::Bundler::Dependency has an extra `should_include?`
  return false unless dependency.should_include? if dependency.respond_to?(:should_include?)

  # Don't return gems added from `add_development_dependency` in a gemspec
  # if the :development group is excluded
  gemspec_source = source.is_a?(::Bundler::Source::Gemspec)
  return false if dependency.type == :development && (!gemspec_source || exclude_development_dependencies?)

  # Gem::Dependency don't have groups - in our usage these objects always
  # come as child-dependencies and are never directly from a Gemfile.
  # We assume that all Gem::Dependencies are ok at this point
  return true if dependency.groups.nil?

  # check if the dependency is in any groups we're interested in
  (dependency.groups & groups).any?
end

#lockfile_pathObject

Returns the path to the Bundler Gemfile.lock



188
189
190
191
# File 'lib/licensed/source/bundler.rb', line 188

def lockfile_path
  return unless gemfile_path
  @lockfile_path ||= gemfile_path.dirname.join("#{gemfile_path.basename}.lock")
end

#recursive_specs(specs, results = Set.new) ⇒ Object

Recursively finds the dependencies for Gem specifications. Returns a ‘Set` containing the package names for all dependencies



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/licensed/source/bundler.rb', line 54

def recursive_specs(specs, results = Set.new)
  return [] if specs.nil? || specs.empty?

  new_specs = Set.new(specs) - results.to_a
  return [] if new_specs.empty?

  results.merge new_specs

  dependency_specs = new_specs.flat_map { |s| specs_for_dependencies(s.dependencies, s.source) }

  return results if dependency_specs.empty?

  results.merge recursive_specs(dependency_specs, results)
end

#specsObject

Returns an array of Gem::Specifications for all gem dependencies



40
41
42
43
44
45
46
47
48
49
50
# File 'lib/licensed/source/bundler.rb', line 40

def specs
  # get the specifications for all dependencies in a Gemfile
  root_dependencies = definition.dependencies.select { |d| include?(d, nil) }
  root_specs = specs_for_dependencies(root_dependencies, nil).compact

  # recursively find the remaining specifications
  all_specs = recursive_specs(root_specs)

  # delete any specifications loaded from a gemspec
  all_specs.delete_if { |s| s.source.is_a?(::Bundler::Source::Gemspec) }
end

#specs_for_dependencies(dependencies, source) ⇒ Object

Returns the specs for dependencies that pass the checks in ‘include?` Raises an error if the specification isn’t found



71
72
73
74
75
76
# File 'lib/licensed/source/bundler.rb', line 71

def specs_for_dependencies(dependencies, source)
  included_dependencies = dependencies.select { |d| include?(d, source) }
  included_dependencies.map do |dep|
    gem_spec(dep) || raise("Unable to find a specification for #{dep.name} (#{dep.requirement}) in any sources")
  end
end