Class: Dependabot::Julia::FileUpdater

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

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.



35
36
37
38
# File 'lib/dependabot/julia/file_updater.rb', line 35

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.



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

def notices
  @notices
end

Class Method Details

.updated_files_regexObject



19
20
21
# File 'lib/dependabot/julia/file_updater.rb', line 19

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



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/dependabot/julia/file_updater.rb', line 178

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



104
105
106
# File 'lib/dependabot/julia/file_updater.rb', line 104

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

#all_projects_only_update(updated_project_files) ⇒ Object



109
110
111
112
113
114
115
116
117
# File 'lib/dependabot/julia/file_updater.rb', line 109

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



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/dependabot/julia/file_updater.rb', line 210

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



143
144
145
146
147
148
# File 'lib/dependabot/julia/file_updater.rb', line 143

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



157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/dependabot/julia/file_updater.rb', line 157

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)


171
172
173
174
175
# File 'lib/dependabot/julia/file_updater.rb', line 171

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



76
77
78
79
80
81
82
83
# File 'lib/dependabot/julia/file_updater.rb', line 76

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



41
42
43
44
45
46
47
48
# File 'lib/dependabot/julia/file_updater.rb', line 41

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



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/dependabot/julia/file_updater.rb', line 51

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?

    # 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



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/dependabot/julia/file_updater.rb', line 86

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



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/dependabot/julia/file_updater.rb', line 125

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