Class: Kettle::Dev::LockfileReset
- Inherits:
-
Object
- Object
- Kettle::Dev::LockfileReset
- Defined in:
- lib/kettle/dev/lockfile_reset.rb
Constant Summary collapse
- DEFAULT_DISABLED_ENV =
{ "KETTLE_DEV_DEV" => "false", "K_JEM_TEMPLATING" => "false", "STRUCTUREDMERGE_DEV" => "false", "TREE_SITTER_LANGUAGE_PACK_DEV" => "false", "RUBOCOP_LTS_LOCAL" => "false", "GALTZO_FLOSS_DEV" => "false", "UR_BRAIN_DEV" => "false" }.freeze
- RELEASE_LOCKFILES_TARGET =
"release-lockfiles"- SUPPORTED_TARGETS =
["Gemfile.lock", "Appraisal.root.gemfile.lock", RELEASE_LOCKFILES_TARGET].freeze
- UNBUNDLED_ENV_KEYS =
%w[ BUNDLE_BIN_PATH BUNDLE_FROZEN BUNDLER_VERSION RUBYOPT ].freeze
Instance Method Summary collapse
- #diagnostics(path) ⇒ Object
- #display_path(path) ⇒ Object
- #dynamic_local_path_env ⇒ Object
- #empty_registry_checksums(path) ⇒ Object
- #gemfile_for_lockfile(path) ⇒ Object
- #has_any_sha_checksum?(path) ⇒ Boolean
- #has_local_path_remote?(path) ⇒ Boolean
-
#initialize(root:, command_runner:) ⇒ LockfileReset
constructor
A new instance of LockfileReset.
- #local_path_env_value?(value) ⇒ Boolean
- #local_path_remote_lines(path) ⇒ Object
- #lockfile_path_for(target) ⇒ Object
- #lockfile_paths ⇒ Object
- #lockfile_paths_for(target) ⇒ Object
- #normalization_env ⇒ Object
- #normalization_needed?(path) ⇒ Boolean
- #path_dependency_gems(path) ⇒ Object
- #path_source_gems(path) ⇒ Object
- #release_lockfiles_target?(target) ⇒ Boolean
- #reset(target) ⇒ Object
- #reset_command(path:, gemfile:, full_update: false) ⇒ Object
- #reset_lockfile!(path, full_update: false) ⇒ Object
- #reset_update_gems(path) ⇒ Object
- #self_path_source_gems(path) ⇒ Object
- #unreleased_workspace_registry_specs(path) ⇒ Object
Constructor Details
#initialize(root:, command_runner:) ⇒ LockfileReset
Returns a new instance of LockfileReset.
32 33 34 35 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 32 def initialize(root:, command_runner:) @root = root @command_runner = command_runner end |
Instance Method Details
#diagnostics(path) ⇒ Object
103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 103 def diagnostics(path) diagnostics = [] local_path_remote_lines(path).each do |line_number| diagnostics << "#{display_path(path)} has local path remote at line #{line_number}" end empty_registry_checksums(path).each do |name, version, line_number| diagnostics << "#{display_path(path)} CHECKSUMS has no sha256 for #{name} #{version} at line #{line_number}" end unreleased_workspace_registry_specs(path).each do |name, version, line_number| diagnostics << "#{display_path(path)} locks local workspace gem #{name} #{version} as a registry gem, but that version is not released on RubyGems.org at line #{line_number}" end diagnostics end |
#display_path(path) ⇒ Object
277 278 279 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 277 def display_path(path) Kettle::Dev.display_path(path) end |
#dynamic_local_path_env ⇒ Object
165 166 167 168 169 170 171 172 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 165 def dynamic_local_path_env ENV.each_with_object({}) do |(key, value), env| next unless key.end_with?("_DEV", "_LOCAL") next unless local_path_env_value?(value) env[key] = "false" end end |
#empty_registry_checksums(path) ⇒ Object
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 203 def empty_registry_checksums(path) path_gems = path_source_gems(path) | path_dependency_gems(path) | self_path_source_gems(path) in_checksums = false entries = File.readlines(path).filter_map.with_index(1) do |line, index| stripped = line.strip if stripped == "CHECKSUMS" in_checksums = true next end next unless in_checksums next if stripped.empty? next if stripped == "BUNDLED WITH" match = stripped.match(/\A([A-Za-z0-9_.-]+) \(([^)]+)\)(?:\s+(sha256=.*))?\z/) next unless match name = match[1] checksum = match[3].to_s next unless checksum.empty? next if path_gems.include?(name) [name, match[2], index] end return [] unless has_any_sha_checksum?(path) entries end |
#gemfile_for_lockfile(path) ⇒ Object
154 155 156 157 158 159 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 154 def gemfile_for_lockfile(path) basename = File.basename(path) return File.join(root, "Gemfile") if basename == "Gemfile.lock" path.delete_suffix(".lock") end |
#has_any_sha_checksum?(path) ⇒ Boolean
243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 243 def has_any_sha_checksum?(path) in_checksums = false File.readlines(path).any? do |line| stripped = line.strip if stripped == "CHECKSUMS" in_checksums = true next false end next false unless in_checksums stripped.include?("sha256=") end end |
#has_local_path_remote?(path) ⇒ Boolean
182 183 184 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 182 def has_local_path_remote?(path) !local_path_remote_lines(path).empty? end |
#local_path_env_value?(value) ⇒ Boolean
174 175 176 177 178 179 180 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 174 def local_path_env_value?(value) text = value.to_s.strip return false if text.empty? || text.casecmp("false").zero? return true if text.start_with?("/", "./", "../", "~") text.include?(File::SEPARATOR) end |
#local_path_remote_lines(path) ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 186 def local_path_remote_lines(path) in_path = false File.readlines(path).filter_map.with_index(1) do |line, index| stripped = line.strip if stripped.match?(/\A[A-Z][A-Z ]*\z/) in_path = stripped == "PATH" next end next unless in_path next unless stripped.start_with?("remote:") next if stripped == "remote: ." next unless stripped.start_with?("remote: /", "remote: ./", "remote: ../") index end end |
#lockfile_path_for(target) ⇒ Object
125 126 127 128 129 130 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 125 def lockfile_path_for(target) paths = lockfile_paths_for(target) raise Error, "reset target #{target.inspect} resolves to multiple lockfiles; use lockfile_paths_for" if paths.length != 1 paths.first end |
#lockfile_paths ⇒ Object
117 118 119 120 121 122 123 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 117 def lockfile_paths candidates = [ File.join(root, "Gemfile.lock"), File.join(root, "Appraisal.root.gemfile.lock") ] candidates.select { |path| File.file?(path) }.sort end |
#lockfile_paths_for(target) ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 132 def lockfile_paths_for(target) normalized = target.to_s.strip raise Error, "reset requires TARGET" if normalized.empty? if release_lockfiles_target?(normalized) paths = lockfile_paths raise Error, "no release lockfiles found" if paths.empty? return paths end supported = SUPPORTED_TARGETS.reject { |candidate| candidate == RELEASE_LOCKFILES_TARGET } match = supported.find { |candidate| candidate.casecmp(normalized).zero? } unless match raise Error, "reset target #{normalized.inspect} is not supported; supported targets: #{SUPPORTED_TARGETS.join(", ")}" end [File.join(root, match)] end |
#normalization_env ⇒ Object
161 162 163 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 161 def normalization_env DEFAULT_DISABLED_ENV.merge(dynamic_local_path_env) end |
#normalization_needed?(path) ⇒ Boolean
99 100 101 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 99 def normalization_needed?(path) has_local_path_remote?(path) || !empty_registry_checksums(path).empty? || !unreleased_workspace_registry_specs(path).empty? end |
#path_dependency_gems(path) ⇒ Object
264 265 266 267 268 269 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 264 def path_dependency_gems(path) lockfile_parser(path).dependencies.each_value.each_with_object(Set.new) do |dependency, gems| source = dependency.source gems << dependency.name if path_source?(source) && !self_path_source?(source) end end |
#path_source_gems(path) ⇒ Object
257 258 259 260 261 262 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 257 def path_source_gems(path) lockfile_parser(path).specs.each_with_object(Set.new) do |spec, gems| source = spec.source gems << spec.name if path_source?(source) && !self_path_source?(source) end end |
#release_lockfiles_target?(target) ⇒ Boolean
150 151 152 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 150 def release_lockfiles_target?(target) target.to_s.strip.casecmp(RELEASE_LOCKFILES_TARGET).zero? end |
#reset(target) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 37 def reset(target) paths = lockfile_paths_for(target) force_full_update = release_lockfiles_target?(target) uninstall_unreleased_local_gems(paths) if force_full_update paths.each do |path| reset_lockfile!(path, full_update: force_full_update) if force_full_update || normalization_needed?(path) end if force_full_update && uninstall_unreleased_local_gems(paths) paths.each do |path| reset_lockfile!(path, full_update: true) end end diagnostics = paths.flat_map { |path| diagnostics(path) } raise Error, (target, diagnostics) unless diagnostics.empty? paths end |
#reset_command(path:, gemfile:, full_update: false) ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 70 def reset_command(path:, gemfile:, full_update: false) update_gems = (full_update || has_local_path_remote?(path)) ? [] : reset_update_gems(path) env = normalization_env.merge( "BUNDLE_GEMFILE" => gemfile, "BUNDLE_LOCKFILE" => path ) command = +"env" UNBUNDLED_ENV_KEYS.each do |key| command << " -u #{key}" end env.each do |key, value| command << " #{key}=#{Shellwords.escape(value)}" end command << " bundle lock" command << " --update" command << " #{update_gems.map { |gem_name| Shellwords.escape(gem_name) }.join(" ")}" unless update_gems.empty? command << " --add-checksums" command end |
#reset_lockfile!(path, full_update: false) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 55 def reset_lockfile!(path, full_update: false) gemfile = gemfile_for_lockfile(path) unless gemfile && File.file?(gemfile) warn("Cannot reset #{display_path(path)} because its Gemfile was not found.") return end command = reset_command(path: path, gemfile: gemfile, full_update: full_update) if full_update || has_local_path_remote?(path) rebuild_lockfile(path) { command_runner.call(command) } else command_runner.call(command) end end |
#reset_update_gems(path) ⇒ Object
90 91 92 93 94 95 96 97 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 90 def reset_update_gems(path) ( path_source_gems(path).to_a | path_dependency_gems(path).to_a | empty_registry_checksums(path).map(&:first) | unreleased_workspace_registry_specs(path).map(&:first) ).uniq.sort end |
#self_path_source_gems(path) ⇒ Object
271 272 273 274 275 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 271 def self_path_source_gems(path) lockfile_parser(path).specs.each_with_object(Set.new) do |spec, gems| gems << spec.name if self_path_source?(spec.source) end end |
#unreleased_workspace_registry_specs(path) ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 231 def unreleased_workspace_registry_specs(path) local_names = local_workspace_gem_names return [] if local_names.empty? registry_specs_with_lines(path).filter_map do |name, version, line_number| next unless local_names.include?(name) next if ruby_gems_version_available?(name, version) [name, version, line_number] end end |