Class: RBS::Collection::Config::LockfileGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/rbs/collection/config/lockfile_generator.rb,
sig/collection/config/lockfile_generator.rbs

Defined Under Namespace

Classes: GemfileLockMismatchError

Constant Summary collapse

ALUMNI_STDLIBS =

Name of stdlibs that was rbs-bundled stdlib but is now a gem.

Returns:

  • (Hash[String, String?])
{
  "mutex_m" => ">= 0.3.0",
  "abbrev" => nil,
  "base64" => nil,
  "benchmark" => nil,
  "bigdecimal" => nil,
  "csv" => nil,
  "kconv" => nil,
  "logger" => nil,
  "minitest" => nil,
  "net-smtp" => nil,
  "nkf" => nil,
  "observer" => nil,
  "cgi" => nil,
  "pstore" => nil,
}
NONGEM_STDLIBS =

Returns:

  • (Set[String])
Set[
  "cgi-escape",
  "coverage",
  "monitor",
  "objspace",
  "pathname",
  "pty",
  "ripper",
  "socket",
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config:, definition:, with_lockfile:) ⇒ LockfileGenerator

Returns a new instance of LockfileGenerator.

Parameters:



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rbs/collection/config/lockfile_generator.rb', line 59

def initialize(config:, definition:, with_lockfile:)
  @config = config

  @gem_entries = config.gems.each.with_object({}) do |entry, hash| #$ Hash[String, gem_entry?]
    name = entry["name"]
    hash[name] = entry
  end

  lockfile_path = Config.to_lockfile_path(config.config_path)
  lockfile_dir = lockfile_path.parent

  @lockfile = Lockfile.new(
    lockfile_path: lockfile_path,
    path: config.repo_path_data,
    gemfile_lock_path: definition.lockfile.relative_path_from(lockfile_dir)
  )

  if with_lockfile && lockfile_path.file?
    @existing_lockfile = Lockfile.from_lockfile(lockfile_path: lockfile_path, data: YAML.load_file(lockfile_path.to_s))
    validate_gemfile_lock_path!(lock: @existing_lockfile, gemfile_lock_path: definition.lockfile)
  end

  @definition = definition
  @gem_hash = definition.locked_gems.specs.each.with_object({}) do |spec, hash|  #$ Hash[String, Bundler::LazySpecification]
    hash[spec.name] = spec
  end
end

Instance Attribute Details

#configConfig (readonly)

Returns the value of attribute config.

Returns:



51
52
53
# File 'lib/rbs/collection/config/lockfile_generator.rb', line 51

def config
  @config
end

#definitionBundler::Definition (readonly)

Returns the value of attribute definition.

Returns:



51
52
53
# File 'lib/rbs/collection/config/lockfile_generator.rb', line 51

def definition
  @definition
end

#existing_lockfileLockfile? (readonly)

Returns the value of attribute existing_lockfile.

Returns:



51
52
53
# File 'lib/rbs/collection/config/lockfile_generator.rb', line 51

def existing_lockfile
  @existing_lockfile
end

#gem_entriesHash[String, gem_entry?] (readonly)

A hash table to look up a gem entry in collection config from the name of the gem

Returns:

  • (Hash[String, gem_entry?])


32
33
34
# File 'sig/collection/config/lockfile_generator.rbs', line 32

def gem_entries
  @gem_entries
end

#gem_hashHash[String, Bundler::LazySpecification] (readonly)

A hash table to look up a spec from name of the gem

Returns:



29
30
31
# File 'sig/collection/config/lockfile_generator.rbs', line 29

def gem_hash
  @gem_hash
end

#lockfileLockfile (readonly)

Returns the value of attribute lockfile.

Returns:



51
52
53
# File 'lib/rbs/collection/config/lockfile_generator.rb', line 51

def lockfile
  @lockfile
end

Class Method Details

.generate(config:, definition:, with_lockfile: true) ⇒ Lockfile

Parameters:

Returns:



53
54
55
56
57
# File 'lib/rbs/collection/config/lockfile_generator.rb', line 53

def self.generate(config:, definition:, with_lockfile: true)
  generator = new(config: config, definition: definition, with_lockfile: with_lockfile)
  generator.generate
  generator.lockfile
end

Instance Method Details

#assign_gem(name:, version:, skip: false) ⇒ void

This method returns an undefined value.

Inserts a entry to lockfile of a gem and its dependencies, if not included in ignored_gems:

  • If skip: is true, it skips adding the gem, but adds it's dependencies.

Parameters:

  • name: (String)
  • version: (String, nil)
  • skip: (Boolean) (defaults to: false)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
109
110
111
112
113
114
115
116
# File 'sig/collection/config/lockfile_generator.rbs', line 50

private def assign_gem(name:, version:, skip: false)
  entry = gem_entries[name]
  src_data = entry&.fetch("source", nil)
  ignored = entry&.fetch("ignore", false)

  return if ignored
  return if lockfile.gems.key?(name)

  unless skip
    # @type var locked: Lockfile::library?

    if existing_lockfile
      locked = existing_lockfile.gems[name]
    end

    # If rbs_collection.lock.yaml contain the gem, use it.
    # Else find the gem from gem_collection.
    unless locked
      source =
        if src_data
          Sources.from_config_entry(src_data, base_directory: config.config_path.dirname)
        else
          find_source(name: name)
        end

      if source.is_a?(Sources::Stdlib)
        assign_stdlib(name: name)
        return
      end

      if source
        installed_version = version
        best_version = find_best_version(version: installed_version, versions: source.versions(name))

        locked = {
          name: name,
          version: best_version.to_s,
          source: source,
        }
      end
    end

    if locked
      lockfile.gems[name] = locked

      begin
        locked[:source].dependencies_of(locked[:name], locked[:version])&.each do |dep|
          assign_stdlib(name: dep["name"], from_gem: name)
        end
      rescue
        RBS.logger.warn "Cannot find `#{locked[:name]}-#{locked[:version]}` gem. Using incorrect Bundler context? (#{definition.lockfile})"
      end
    end
  end

  if spec = gem_hash.fetch(name, nil)
    spec.dependencies.each do |dep|
      if dep_spec = gem_hash[dep.name]
        assign_gem(name: dep.name, version: dep_spec.version)
      end
    end
  else
    unless NONGEM_STDLIBS.include?(name)
      RBS.logger.warn "Cannot find `#{name}` gem. Using incorrect Bundler context? (#{definition.lockfile})"
    end
  end
end

#assign_stdlib(name:, from_gem: nil) ⇒ void

This method returns an undefined value.

Assign a gem from stdlib to lockfile

from_gem is a String when the stdlib is assigned through the dependencies: in manifest.yaml of a gem.

Parameters:

  • name: (String)
  • from_gem: (String) (defaults to: nil)


56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
109
110
111
112
113
114
115
116
117
118
119
# File 'sig/collection/config/lockfile_generator.rbs', line 56

private def assign_stdlib(name:, from_gem: nil)
  return if lockfile.gems.key?(name)

  case name
  when 'bigdecimal-math', 'kconv'
    # These gems are never released as a gem.
    # Therefore, `assign_gem` should not be called.
    RBS.logger.info {
      from = from_gem || "rbs_collection.yaml"
      "`#{name}` is included in the RBS dependencies of `#{from}`, but the type definition as a stdlib in rbs-gem is deprecated. Delete `#{name}` from the RBS dependencies of `#{from}`."
    }
    source = find_source(name: name)
    if source&.is_a?(Sources::Stdlib)
      lockfile.gems[name] = { name: name, version: "0", source: source }
    end
    return
  when 'set'
    # set is migrated to core from stdlib.
    RBS.logger.info {
      from = from_gem || "rbs_collection.yaml"
      "`#{name}` is a part of the Ruby core library. The dependency to the library can be safely deleted from #{from}."
    }
    return
  when *ALUMNI_STDLIBS.keys
    version = ALUMNI_STDLIBS.fetch(name)
    if from_gem
      # From `dependencies:` of a `manifest.yaml` of a gem
      source = find_source(name: name) or raise
      if source.is_a?(Sources::Stdlib) && version
        RBS.logger.warn {
          "`#{name}` is included in the RBS dependencies of `#{from_gem}`, but the type definition as a stdlib in rbs-gem is deprecated. Add `#{name}` (#{version}) to the dependency of your Ruby program to use the gem-bundled type definition."
        }
      else
        RBS.logger.info {
          "`#{name}` is included in the RBS dependencies of `#{from_gem}`, but the type definition as a stdlib in rbs-gem is deprecated. Delete `#{name}` from the RBS dependencies of `#{from_gem}`."
        }
        assign_gem(name: name, version: nil)
        return
      end
    else
      # From `gems:` of a `rbs_collection.yaml`
      RBS.logger.warn {
        if version
          "`#{name}` as a stdlib in rbs-gem is deprecated. Add `#{name}` (#{version}) to the dependency of your Ruby program to use the gem-bundled type definition."
        else
          "`#{name}` as a stdlib in rbs-gem is deprecated. Delete `#{name}` from the RBS dependencies in your rbs_collection.yaml."
        end
      }
    end
  end

  source = Sources::Stdlib.instance
  lockfile.gems[name] = { name: name, version: "0", source: source }

  unless source.has?(name, nil)
    raise "Cannot find `#{name}` from standard libraries"
  end

  if deps = source.dependencies_of(name, "0")
    deps.each do |dep|
      assign_stdlib(name: dep["name"], from_gem: name)
    end
  end
end

#find_best_version(version:, versions:) ⇒ Gem::Version

Parameters:

  • version: (String, nil)
  • versions: (Array[String])

Returns:

  • (Gem::Version)


259
260
261
262
263
264
265
# File 'lib/rbs/collection/config/lockfile_generator.rb', line 259

private def find_best_version(version:, versions:)
  candidates = versions.map { |v| Gem::Version.create(v) or raise }
  return candidates.max || raise unless version

  v = Gem::Version.create(version) or raise
  Repository.find_best_version(v, candidates)
end

#find_source(name:) ⇒ Sources::t?

Find a source of a gem from ones registered in config.sources

Returns nil if no source contains the definition of the gem.

Parameters:

  • name: (String)

Returns:

  • (Sources::t, nil)


62
63
64
65
66
# File 'sig/collection/config/lockfile_generator.rbs', line 62

private def find_source(name:)
  sources = config.sources

  sources.find { |c| c.has?(name, nil) }
end

#generatevoid

This method returns an undefined value.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rbs/collection/config/lockfile_generator.rb', line 87

def generate
  config.gems.each do |gem|
    case
    when gem.dig("source", "type") == "stdlib"
      unless gem.fetch("ignore", false)
        assign_stdlib(name: gem["name"])
      end
    else
      assign_gem(name: gem["name"], version: gem["version"])
    end
  end

  definition.dependencies.each do |dep|
    if dep.autorequire && dep.autorequire.empty?
      next
    end

    if spec = gem_hash[dep.name]
      assign_gem(name: dep.name, version: spec.version, skip: dep.source.is_a?(Bundler::Source::Gemspec))
    end
  end

  lockfile.lockfile_path.write(YAML.dump(lockfile.to_lockfile))
end

#validate_gemfile_lock_path!(lock:, gemfile_lock_path:) ⇒ void

This method returns an undefined value.

Ensure if current gemfile_lock_path is the same with the path saved in lock

Parameters:

  • lock: (Lockfile, nil)
  • gemfile_lock_path: (Pathname)


44
45
46
47
48
49
50
# File 'sig/collection/config/lockfile_generator.rbs', line 44

private def validate_gemfile_lock_path!(lock:, gemfile_lock_path:)
  return unless lock
  return unless lock.gemfile_lock_fullpath
  unless File.realpath(lock.gemfile_lock_fullpath) == File.realpath(gemfile_lock_path)
    raise GemfileLockMismatchError.new(expected: lock.gemfile_lock_fullpath, actual: gemfile_lock_path)
  end
end