Class: Dependabot::Julia::FileUpdater

Inherits:
FileUpdaters::Base
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/dependabot/julia/file_updater.rb

Constant Summary collapse

COMPAT_HEADER_PATTERN =

Matches a [compat] table header, tolerating indentation and a trailing comment ("[compat] # pins")

T.let(/^\s*\[compat\]\s*(?:#.*)?$/, Regexp)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dependencies:, dependency_files:, credentials:, repo_contents_path: nil, options: {}) ⇒ FileUpdater

Returns a new instance of FileUpdater.



39
40
41
42
# File 'lib/dependabot/julia/file_updater.rb', line 39

def initialize(dependencies:, dependency_files:, credentials:, repo_contents_path: nil, options: {})
  super
  @notices = T.let([], T::Array[Dependabot::Notice])
end

Instance Attribute Details

#noticesObject (readonly)

Returns the value of attribute notices.



28
29
30
# File 'lib/dependabot/julia/file_updater.rb', line 28

def notices
  @notices
end

Class Method Details

.updated_files_regexObject



23
24
25
# File 'lib/dependabot/julia/file_updater.rb', line 23

def self.updated_files_regex
  [/(?:Julia)?Project\.toml$/i, /(?:Julia)?Manifest(?:-v[\d.]+)?\.toml$/i]
end

Instance Method Details

#add_manifest_update_notice(manifest_path, error_message) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/dependabot/julia/file_updater.rb', line 186

def add_manifest_update_notice(manifest_path, error_message)
  # Resolve relative paths to absolute paths for clarity in user-facing notices
  # Use Pathname.cleanpath to handle any depth of relative paths (e.g., ../../Manifest.toml)
  project_dir = T.must(project_file).directory
  absolute_manifest_path = if manifest_path.start_with?("../", "./")
                             # For workspace packages, compute the absolute path
                             Pathname.new(File.join(project_dir, manifest_path)).cleanpath.to_s
                           else
                             # For regular packages, use the manifest path as-is
                             File.join(project_dir, manifest_path)
                           end

  @notices << Dependabot::Notice.new(
    mode: Dependabot::Notice::NoticeMode::WARN,
    type: "julia_manifest_not_updated",
    package_manager_name: "Pkg",
    title: "Could not update manifest #{absolute_manifest_path}",
    description: "The Julia package manager failed to update the new dependency versions " \
                 "in `#{absolute_manifest_path}`:\n\n```\n#{error_message}\n```",
    show_in_pr: true,
    show_alert: true
  )
end

#all_project_filesObject



112
113
114
# File 'lib/dependabot/julia/file_updater.rb', line 112

def all_project_files
  dependency_files.select { |f| f.name.match?(/Project\.toml$/i) }
end

#all_projects_only_update(updated_project_files) ⇒ Object



117
118
119
120
121
122
123
124
125
# File 'lib/dependabot/julia/file_updater.rb', line 117

def all_projects_only_update(updated_project_files)
  updated_project_files.filter_map do |update_info|
    file = T.cast(update_info[:file], Dependabot::DependencyFile)
    content = T.cast(update_info[:content], String)
    next if content == file.content

    updated_file(file: file, content: content)
  end
end

#build_updated_files_multi(updated_files, updated_project_files, actual_manifest, result) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/dependabot/julia/file_updater.rb', line 218

def build_updated_files_multi(updated_files, updated_project_files, actual_manifest, result)
  # Add all updated project files
  updated_project_files.each do |update_info|
    file = T.cast(update_info[:file], Dependabot::DependencyFile)
    content = T.cast(update_info[:content], String)
    next if content == file.content

    updated_files << updated_file(file: file, content: content)
  end

  return unless result["manifest_content"]

  updated_manifest_content = result["manifest_content"]
  return unless updated_manifest_content != actual_manifest.content

  manifest_for_update = if result["manifest_path"]
                          manifest_file_for_path(result["manifest_path"])
                        else
                          actual_manifest
                        end
  updated_files << updated_file(file: manifest_for_update, content: updated_manifest_content)
end

#call_julia_helperObject



151
152
153
154
155
156
# File 'lib/dependabot/julia/file_updater.rb', line 151

def call_julia_helper
  registry_client.update_manifest(
    project_path: Dir.pwd,
    updates: build_updates_hash
  )
end

#handle_julia_helper_error_multi(result, actual_manifest, updated_project_files) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/dependabot/julia/file_updater.rb', line 165

def handle_julia_helper_error_multi(result, actual_manifest, updated_project_files)
  error_message = result["error"]
  manifest_path = actual_manifest.name

  is_resolver_error = resolver_error?(error_message)
  raise error_message unless is_resolver_error

  add_manifest_update_notice(manifest_path, error_message)

  # Return all updated Project.toml files
  all_projects_only_update(updated_project_files)
end

#resolver_error?(error_message) ⇒ Boolean

Returns:

  • (Boolean)


179
180
181
182
183
# File 'lib/dependabot/julia/file_updater.rb', line 179

def resolver_error?(error_message)
  error_message.start_with?("Pkg resolver error:") ||
    error_message.include?("Unsatisfiable requirements") ||
    error_message.include?("ResolverError")
end

#update_all_project_filesObject



84
85
86
87
88
89
90
91
# File 'lib/dependabot/julia/file_updater.rb', line 84

def update_all_project_files
  all_project_files.map do |proj_file|
    {
      file: proj_file,
      content: updated_project_content_for_file(proj_file)
    }
  end
end

#updated_dependency_filesObject



45
46
47
48
49
50
51
52
# File 'lib/dependabot/julia/file_updater.rb', line 45

def updated_dependency_files
  # If no project file, cannot proceed
  raise "No Project.toml file found" unless project_file

  # Use DependabotHelper.jl for manifest updating
  # This works for both standard packages and workspace packages
  updated_files_with_julia_helper
end

#updated_files_with_julia_helperObject



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
# File 'lib/dependabot/julia/file_updater.rb', line 55

def updated_files_with_julia_helper
  updated_files = []

  SharedHelpers.in_a_temporary_repo_directory(T.must(dependency_files.first).directory, repo_contents_path) do
    # Update all project files (main + workspace members)
    updated_project_files = update_all_project_files
    actual_manifest = find_manifest_file

    return all_projects_only_update(updated_project_files) if actual_manifest.nil?

    # Requirement-only updates (no target versions) have nothing to tell
    # Pkg; ship the Project.toml changes on their own.
    return all_projects_only_update(updated_project_files) if build_updates_hash.empty?

    # Write all updated project files to disk for Julia's Pkg
    write_all_temporary_files(updated_project_files, actual_manifest)
    result = call_julia_helper

    return handle_julia_helper_error_multi(result, actual_manifest, updated_project_files) if result["error"]

    build_updated_files_multi(updated_files, updated_project_files, actual_manifest, result)
  end

  raise "No files changed!" if updated_files.empty?

  updated_files
end

#updated_project_content_for_file(proj_file) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/dependabot/julia/file_updater.rb', line 94

def updated_project_content_for_file(proj_file)
  content = T.must(proj_file.content)

  dependencies.each do |dependency|
    # Find the new requirement for this dependency in this file
    new_requirement = dependency.requirements
                                .find { |req| T.cast(req[:file], String) == proj_file.name }
                                &.fetch(:requirement)

    next unless new_requirement

    content = update_dependency_requirement_in_content(content, dependency.name, new_requirement)
  end

  content
end

#write_all_temporary_files(updated_project_files, actual_manifest) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/dependabot/julia/file_updater.rb', line 133

def write_all_temporary_files(updated_project_files, actual_manifest)
  # Write all updated project files
  updated_project_files.each do |update_info|
    file = T.cast(update_info[:file], Dependabot::DependencyFile)
    content = T.cast(update_info[:content], String)

    file_path = file.name
    FileUtils.mkdir_p(File.dirname(file_path)) if file_path.include?("/")
    File.write(file_path, content)
  end

  # Write manifest file
  manifest_path = actual_manifest.name
  FileUtils.mkdir_p(File.dirname(manifest_path)) if manifest_path.include?("/")
  File.write(manifest_path, actual_manifest.content)
end