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
- #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.
29 30 31 32 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 29 def initialize(root:, command_runner:) @root = root @command_runner = command_runner end |
Instance Method Details
#diagnostics(path) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 101 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
276 277 278 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 276 def display_path(path) Kettle::Dev.display_path(path) end |
#dynamic_local_path_env ⇒ Object
163 164 165 166 167 168 169 170 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 163 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
201 202 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 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 201 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
152 153 154 155 156 157 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 152 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
242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 242 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
180 181 182 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 180 def has_local_path_remote?(path) !local_path_remote_lines(path).empty? end |
#local_path_env_value?(value) ⇒ Boolean
172 173 174 175 176 177 178 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 172 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
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 184 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
123 124 125 126 127 128 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 123 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
115 116 117 118 119 120 121 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 115 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
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 130 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
159 160 161 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 159 def normalization_env DEFAULT_DISABLED_ENV.merge(dynamic_local_path_env) end |
#normalization_needed?(path) ⇒ Boolean
97 98 99 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 97 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
263 264 265 266 267 268 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 263 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
256 257 258 259 260 261 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 256 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
148 149 150 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 148 def release_lockfiles_target?(target) target.to_s.strip.casecmp(RELEASE_LOCKFILES_TARGET).zero? end |
#reset(target) ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 34 def reset(target) BundlerEnvGuard.warn_unexpected_env! 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
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 68 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
53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 53 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
88 89 90 91 92 93 94 95 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 88 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
270 271 272 273 274 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 270 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
229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/kettle/dev/lockfile_reset.rb', line 229 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 |