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 =
(BundlerEnvGuard::RESET_ENV_KEYS + %w[ RUBYLIB 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
- #lockfile_platforms(path) ⇒ Object
- #non_registry_source_gems(path) ⇒ Object
- #normalization_env ⇒ Object
- #normalization_needed?(path) ⇒ Boolean
- #path_dependency_gems(path) ⇒ Object
- #path_source_gems(path) ⇒ Object
- #release_lockfiles_target?(target) ⇒ Boolean
- #release_platforms_for(paths) ⇒ Object
- #reset(target) ⇒ Object
- #reset_command(path:, gemfile:, full_update: false, platforms: nil) ⇒ Object
- #reset_lockfile!(path, full_update: false, platforms: nil) ⇒ Object
- #reset_platforms(path) ⇒ Object
- #reset_update_gems(path) ⇒ Object
- #unreleased_workspace_registry_specs(path) ⇒ Object
Constructor Details
#initialize(root:, command_runner:) ⇒ LockfileReset
Returns a new instance of LockfileReset.
30 31 32 33 34 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 30 def initialize(root:, command_runner:) @root = root @command_runner = command_runner @release_platforms = {} end |
Instance Method Details
#diagnostics(path) ⇒ Object
139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 139 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 resolvable from the configured gem source at line #{line_number}" end diagnostics end |
#display_path(path) ⇒ Object
314 315 316 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 314 def display_path(path) Kettle::Dev.display_path(path) end |
#dynamic_local_path_env ⇒ Object
201 202 203 204 205 206 207 208 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 201 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
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 239 def empty_registry_checksums(path) non_registry_gems = non_registry_source_gems(path) | path_dependency_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 non_registry_gems.include?(name) [name, match[2], index] end return [] unless has_any_sha_checksum?(path) entries end |
#gemfile_for_lockfile(path) ⇒ Object
190 191 192 193 194 195 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 190 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
280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 280 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
218 219 220 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 218 def has_local_path_remote?(path) !local_path_remote_lines(path).empty? end |
#local_path_env_value?(value) ⇒ Boolean
210 211 212 213 214 215 216 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 210 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
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 222 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
161 162 163 164 165 166 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 161 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
153 154 155 156 157 158 159 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 153 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
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 168 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 |
#lockfile_platforms(path) ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 111 def lockfile_platforms(path) in_platforms = false File.readlines(path).filter_map do |line| stripped = line.strip if stripped.match?(/\A[A-Z][A-Z ]*\z/) in_platforms = stripped == "PLATFORMS" next end next unless in_platforms next if stripped.empty? stripped end end |
#non_registry_source_gems(path) ⇒ Object
301 302 303 304 305 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 301 def non_registry_source_gems(path) lockfile_parser(path).specs.each_with_object(Set.new) do |spec, gems| gems << spec.name unless registry_source?(spec.source) end end |
#normalization_env ⇒ Object
197 198 199 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 197 def normalization_env DEFAULT_DISABLED_ENV.merge(dynamic_local_path_env) end |
#normalization_needed?(path) ⇒ Boolean
135 136 137 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 135 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
307 308 309 310 311 312 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 307 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
294 295 296 297 298 299 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 294 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
186 187 188 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 186 def release_lockfiles_target?(target) target.to_s.strip.casecmp(RELEASE_LOCKFILES_TARGET).zero? end |
#release_platforms_for(paths) ⇒ Object
105 106 107 108 109 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 105 def release_platforms_for(paths) paths.each_with_object({}) do |path, platforms| platforms[path] = (@release_platforms[path] ||= reset_platforms(path)) end end |
#reset(target) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 36 def reset(target) BundlerEnvGuard.warn_unexpected_env! paths = lockfile_paths_for(target) force_full_update = release_lockfiles_target?(target) platforms = release_platforms_for(paths) if force_full_update uninstall_unreleased_local_gems(paths) if force_full_update paths.each do |path| reset_lockfile!(path, full_update: force_full_update, platforms: platforms&.fetch(path)) 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, platforms: platforms.fetch(path)) 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, platforms: nil) ⇒ Object
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 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 71 def reset_command(path:, gemfile:, full_update: false, platforms: nil) update_gems = (full_update || has_local_path_remote?(path)) ? [] : reset_update_gems(path) platforms ||= reset_platforms(path) removed_platforms = full_update ? [] : lockfile_platforms(path) - platforms 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" removed_platforms.sort.each do |platform| command << " --remove-platform=#{Shellwords.escape(platform)}" end platforms.each do |platform| command << " --add-platform=#{Shellwords.escape(platform)}" end 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, platforms: nil) ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 56 def reset_lockfile!(path, full_update: false, platforms: nil) 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, platforms: platforms) if full_update || has_local_path_remote?(path) rebuild_lockfile(path) { command_runner.call(command) } else command_runner.call(command) end end |
#reset_platforms(path) ⇒ Object
99 100 101 102 103 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 99 def reset_platforms(path) platforms = lockfile_platforms(path) platforms = [Gem::Platform.local.to_s] if platforms.empty? platforms.reject(&:empty?).sort end |
#reset_update_gems(path) ⇒ Object
126 127 128 129 130 131 132 133 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 126 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 |
#unreleased_workspace_registry_specs(path) ⇒ Object
267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 267 def unreleased_workspace_registry_specs(path) local_names = local_workspace_gem_names return [] if local_names.empty? source_urls = registry_source_urls(path) registry_specs_with_lines(path).filter_map do |name, version, line_number| next unless local_names.include?(name) next if gem_source_version_available?(name, version, source_urls) [name, version, line_number] end end |