dependabot-julia 0.395.0 → 0.397.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54579433c6beab3470827fa806a2e620123a56fd2d4b52e1b20573558630cb4c
4
- data.tar.gz: 9d9d3d58871d1cfbc2ef1dd47f51628d6b9af1473f0559ebc58783433f5f53d1
3
+ metadata.gz: 22d7169f4f9c92f586ed2d26a1a3de76469344cb572f48cc6d40767ba283ecd9
4
+ data.tar.gz: 5c23460a41984bd7bc0e1614cf4ec2dbe52a10bdc2bde328ff169ca75ef8e486
5
5
  SHA512:
6
- metadata.gz: 84c9be79e65dd54336e7d4edf6547099dcc9454f9199774f1b7781f322eafd869622740d7197183dcf35211495cd1d712fdc7aac0e27d322064119d0902117d4
7
- data.tar.gz: c94d643415848f2ff46c5c69303321a535404f64eb53a39ca1ba39035a528711a9ecf2ecde5c1b8835e851bf390469a7ed58d0c0363f8e87798166f0d696e4ca
6
+ metadata.gz: 7dd64c80617989cbb44e0c80cf744a2250ce158556607e9070ffd2364ec1e949b62c77c5e3e70e53b6fd221110aa4b8da30192510bd255f7c9cfba6b62633a82
7
+ data.tar.gz: 9c3c6c62bac976740ea9871380d9ac00d5d8fee5bb54c42f79fbfc273f233d17dff453441fdd372270a1aae64cb22e95559171573fac5a297780b669f7f36169
@@ -105,10 +105,7 @@ module Dependabot
105
105
  def project_file_dependencies
106
106
  dependencies_map = T.let({}, T::Hash[String, Dependabot::Dependency])
107
107
 
108
- parsed_projects = all_project_files.filter_map do |proj_file|
109
- result = parse_project_file(proj_file)
110
- [proj_file, result] if result
111
- end
108
+ parsed_projects = parse_project_files
112
109
 
113
110
  # Packages that are themselves workspace projects (the root package or
114
111
  # a sibling member) resolve by path within the workspace, never from a
@@ -148,6 +145,11 @@ module Dependabot
148
145
  return if versions.empty?
149
146
 
150
147
  dependencies_map.transform_values! do |dep|
148
+ # Julia pins a stdlib itself, so its manifest entry is not something
149
+ # Dependabot can bump; with no version recorded only the compat
150
+ # entry is maintained
151
+ next dep if dep.metadata.key?(:julia_stdlib_versions)
152
+
151
153
  uuid = T.cast(dep.metadata[:julia_uuid], T.nilable(String))
152
154
  version = uuid && versions[uuid]
153
155
  next dep unless version
@@ -206,30 +208,28 @@ module Dependabot
206
208
  end
207
209
  end
208
210
 
209
- sig do
210
- params(proj_file: Dependabot::DependencyFile)
211
- .returns(T.nilable(Dependabot::Julia::RegistryClient::Result::Project))
212
- end
213
- def parse_project_file(proj_file)
214
- temp_dir = Dir.mktmpdir("julia_project")
215
- # File names like "../Project.toml" (a workspace root fetched from a
216
- # member directory) must not escape the temp dir; fall back to the
217
- # basename since each project file gets its own directory anyway.
218
- project_path = File.expand_path(File.join(temp_dir, proj_file.name))
219
- unless project_path.start_with?("#{File.expand_path(temp_dir)}#{File::SEPARATOR}")
220
- project_path = File.join(temp_dir, File.basename(proj_file.name))
221
- end
222
- FileUtils.mkdir_p(File.dirname(project_path))
223
- File.write(project_path, proj_file.content)
224
-
225
- begin
226
- result = registry_client.parse_project(project_path: project_path)
211
+ # The project files are laid out together as in the repository so that
212
+ # the helper can find a member's workspace root (or a test
213
+ # environment's package) and bound it by that parent's julia compat.
214
+ sig { returns(T::Array[[Dependabot::DependencyFile, Dependabot::Julia::RegistryClient::Result::Project]]) }
215
+ def parse_project_files
216
+ Dir.mktmpdir("julia_project") do |temp_dir|
217
+ project_paths = all_project_files.map do |proj_file|
218
+ # DependencyFile#path resolves a name like "../Project.toml" (a
219
+ # workspace root fetched from a member directory) against the
220
+ # file's directory, so it cannot escape the temp dir
221
+ project_path = File.join(temp_dir, proj_file.path)
222
+ FileUtils.mkdir_p(File.dirname(project_path))
223
+ File.write(project_path, proj_file.content)
224
+ [proj_file, project_path]
225
+ end
227
226
 
228
- return nil if result.is_a?(Dependabot::Julia::RegistryClient::Result::Failure)
227
+ project_paths.filter_map do |proj_file, project_path|
228
+ result = registry_client.parse_project(project_path: project_path)
229
+ next if result.is_a?(Dependabot::Julia::RegistryClient::Result::Failure)
229
230
 
230
- result
231
- ensure
232
- FileUtils.rm_rf(temp_dir)
231
+ [proj_file, result]
232
+ end
233
233
  end
234
234
  end
235
235
 
@@ -250,7 +250,7 @@ module Dependabot
250
250
  uuid = dependency.uuid
251
251
  requirement_string = dependency.requirement
252
252
 
253
- next if skip_dependency?(uuid, requirement_string, file_name, workspace_package_uuids)
253
+ next if skip_dependency?(dependency, file_name, workspace_package_uuids)
254
254
 
255
255
  new_requirement = {
256
256
  requirement: requirement_string,
@@ -270,7 +270,7 @@ module Dependabot
270
270
  version: nil,
271
271
  requirements: existing_requirements,
272
272
  package_manager: "julia",
273
- metadata: existing_dep.metadata
273
+ metadata: dependency_metadata(dependency, file_name, existing_dep.metadata)
274
274
  )
275
275
  else
276
276
  # Create new dependency
@@ -279,12 +279,28 @@ module Dependabot
279
279
  version: nil,
280
280
  requirements: [new_requirement],
281
281
  package_manager: "julia",
282
- metadata: { julia_uuid: uuid }
282
+ metadata: dependency_metadata(dependency, file_name, { julia_uuid: uuid })
283
283
  )
284
284
  end
285
285
  end
286
286
  end
287
287
 
288
+ # A stdlib carries the versions its compat entry has to admit, keyed by
289
+ # project file since every file has its own effective julia range
290
+ sig do
291
+ params(
292
+ dependency: Dependabot::Julia::RegistryClient::Result::ProjectDependency,
293
+ file_name: String,
294
+ metadata: T::Hash[Symbol, T.untyped]
295
+ ).returns(T::Hash[Symbol, T.untyped])
296
+ end
297
+ def dependency_metadata(dependency, file_name, metadata)
298
+ return metadata unless dependency.stdlib
299
+
300
+ versions_by_file = T.cast(metadata[:julia_stdlib_versions], T.nilable(T::Hash[String, T::Array[String]])) || {}
301
+ metadata.merge(julia_stdlib_versions: versions_by_file.merge(file_name => dependency.stdlib_versions))
302
+ end
303
+
288
304
  # UUID is a package's identity in Julia: two same-named entries with
289
305
  # different UUIDs are different packages, and merging them would run
290
306
  # updates against the wrong UUID.
@@ -313,20 +329,34 @@ module Dependabot
313
329
 
314
330
  sig do
315
331
  params(
316
- uuid: T.nilable(String),
317
- requirement_string: T.nilable(String),
332
+ dependency: Dependabot::Julia::RegistryClient::Result::ProjectDependency,
318
333
  file_name: String,
319
334
  workspace_package_uuids: T::Array[String]
320
335
  ).returns(T::Boolean)
321
336
  end
322
- def skip_dependency?(uuid, requirement_string, file_name, workspace_package_uuids)
323
- return true if uuid && workspace_package_uuids.include?(uuid)
337
+ def skip_dependency?(dependency, file_name, workspace_package_uuids)
338
+ return true if workspace_package_uuids.include?(dependency.uuid)
339
+
340
+ # Pkg pins a standard library to the version bundled with Julia, so
341
+ # a compat entry tracking its registry releases (a legacy bridge for
342
+ # older Julia, or an "upgradable" stdlib release) can make the
343
+ # project uninstallable on part of its supported Julia range. The
344
+ # helper flags packages that ship with any Julia release admitted by
345
+ # the project's julia compat and reports the versions the entry has
346
+ # to admit instead; without those there is nothing safe to propose.
347
+ if dependency.stdlib && dependency.stdlib_versions.empty?
348
+ Dependabot.logger.info(
349
+ "Skipping #{dependency.name} in #{file_name}: standard library with no known versions " \
350
+ "for the project's Julia range"
351
+ )
352
+ return true
353
+ end
324
354
 
325
355
  # A dep with no compat entry in a workspace member file (test/,
326
356
  # docs/, ...) must not get one synthesized: Julia convention
327
357
  # (CompatHelper) only adds compat bounds to the package's own
328
358
  # Project.toml. Existing member compat entries are still updated.
329
- requirement_string.nil? && workspace_member_file?(file_name)
359
+ dependency.requirement.nil? && workspace_member_file?(file_name)
330
360
  end
331
361
 
332
362
  # Anything outside the target directory ("test/Project.toml", or
@@ -51,6 +51,23 @@ module Dependabot
51
51
  optional_string_value(hash[key], "#{context} #{key}")
52
52
  end
53
53
 
54
+ sig { params(hash: ObjectHash, key: String, context: String).returns(T::Boolean) }
55
+ def self.optional_boolean(hash, key, context)
56
+ case hash[key]
57
+ when nil, false then false
58
+ when true then true
59
+ else raise TypeError, "#{context} #{key} must be a boolean or nil"
60
+ end
61
+ end
62
+
63
+ sig { params(hash: ObjectHash, key: String, context: String).returns(T::Array[String]) }
64
+ def self.optional_string_array(hash, key, context)
65
+ value = hash[key]
66
+ return [] if value.nil?
67
+
68
+ string_array(value, "#{context} #{key}")
69
+ end
70
+
54
71
  sig { params(value: Object, context: String).returns(String) }
55
72
  def self.string_value(value, context)
56
73
  return value if value.is_a?(String)
@@ -165,6 +182,13 @@ module Dependabot
165
182
  const :name, String
166
183
  const :uuid, String
167
184
  const :requirement, T.nilable(String), default: nil
185
+ # Ships with at least one Julia release admitted by the project's
186
+ # julia compat entry
187
+ const :stdlib, T::Boolean, default: false
188
+ # For a stdlib, the versions its compat entry has to admit across
189
+ # that Julia range (lowest per caret line, sorted); see the helper's
190
+ # stdlib_versions_for_julia_compat
191
+ const :stdlib_versions, T::Array[String], default: []
168
192
 
169
193
  sig { params(value: Object).returns(ProjectDependency) }
170
194
  def self.from_object(value)
@@ -174,7 +198,9 @@ module Dependabot
174
198
  new(
175
199
  name: ValueParser.string(hash, "name", context),
176
200
  uuid: ValueParser.string(hash, "uuid", context),
177
- requirement: ValueParser.optional_string(hash, "requirement", context)
201
+ requirement: ValueParser.optional_string(hash, "requirement", context),
202
+ stdlib: ValueParser.optional_boolean(hash, "stdlib", context),
203
+ stdlib_versions: ValueParser.optional_string_array(hash, "stdlib_versions", context)
178
204
  )
179
205
  end
180
206
  end
@@ -14,10 +14,11 @@ module Dependabot
14
14
  params(
15
15
  requirements: T::Array[Dependabot::DependencyRequirement],
16
16
  target_version: T.nilable(String),
17
- update_strategy: T.nilable(Symbol)
17
+ update_strategy: T.nilable(Symbol),
18
+ stdlib_versions: T::Hash[String, T::Array[String]]
18
19
  ).void
19
20
  end
20
- def initialize(requirements:, target_version:, update_strategy:)
21
+ def initialize(requirements:, target_version:, update_strategy:, stdlib_versions: {})
21
22
  @requirements = T.let(
22
23
  requirements.map { |req| Dependabot::DependencyRequirement.create(req) },
23
24
  T::Array[Dependabot::DependencyRequirement]
@@ -26,16 +27,22 @@ module Dependabot
26
27
  # Julia's ecosystem convention (CompatHelper) is to append a new spec
27
28
  # to the existing compat entry, i.e. widen, so that is the default.
28
29
  @update_strategy = T.let(update_strategy || :widen_ranges, Symbol)
30
+ # For a standard library: the versions its compat entry has to admit,
31
+ # keyed by project file (see FileParser#dependency_metadata)
32
+ @stdlib_versions = stdlib_versions
29
33
  end
30
34
 
31
35
  sig { returns(T::Array[Dependabot::DependencyRequirement]) }
32
36
  def updated_requirements
33
- return requirements unless target_version
34
37
  return requirements if update_strategy == :lockfile_only
35
38
 
36
- target_version_obj = Dependabot::Julia::Version.new(target_version)
39
+ target_version_obj = target_version && Dependabot::Julia::Version.new(target_version)
37
40
 
38
41
  requirements.map do |requirement|
42
+ floor = stdlib_versions.fetch(requirement.file.to_s, [])
43
+ next update_stdlib_requirement(requirement, floor) if floor.any?
44
+ next requirement unless target_version_obj
45
+
39
46
  update_requirement(requirement, target_version_obj)
40
47
  end
41
48
  end
@@ -51,6 +58,60 @@ module Dependabot
51
58
  sig { returns(Symbol) }
52
59
  attr_reader :update_strategy
53
60
 
61
+ sig { returns(T::Hash[String, T::Array[String]]) }
62
+ attr_reader :stdlib_versions
63
+
64
+ # A stdlib's compat entry has to admit every version the project can
65
+ # meet across its Julia range rather than track the registry's latest
66
+ # release, so it is only ever widened to cover the missing ones,
67
+ # whatever strategy is configured.
68
+ sig do
69
+ params(
70
+ requirement: Dependabot::DependencyRequirement,
71
+ floor: T::Array[String]
72
+ ).returns(Dependabot::DependencyRequirement)
73
+ end
74
+ def update_stdlib_requirement(requirement, floor)
75
+ current_requirement = requirement.requirement_string
76
+ # Explicit ranges are manual constraints, as in updated_version_requirement
77
+ return requirement if current_requirement&.match?(Dependabot::Julia::Requirement::HYPHEN_RANGE_PATTERN)
78
+
79
+ versions = floor.map { |version| Dependabot::Julia::Version.new(version) }
80
+ new_requirement = if current_requirement.nil?
81
+ versions.map { |version| stdlib_version_spec(version) }.join(", ")
82
+ else
83
+ widened_stdlib_requirement(current_requirement, versions)
84
+ end
85
+ return requirement if new_requirement == current_requirement
86
+
87
+ Dependabot::DependencyRequirement.create(requirement.merge(requirement: new_requirement))
88
+ end
89
+
90
+ sig { params(requirement_string: String, versions: T::Array[Dependabot::Julia::Version]).returns(String) }
91
+ def widened_stdlib_requirement(requirement_string, versions)
92
+ reqs = Dependabot::Julia::Requirement.requirements_array(requirement_string)
93
+ missing = versions.reject { |version| reqs.any? { |req| req.satisfied_by?(version) } }
94
+
95
+ missing.reduce(requirement_string) do |entry, version|
96
+ append_spec_string(entry, stdlib_version_spec(version))
97
+ end
98
+ end
99
+
100
+ # Same shape as simplified_version_spec, except that "1.0.0" reads as
101
+ # "1" (the stdlib line, not a particular release) and 0.0.0 stands for
102
+ # the old test sandbox pin, written as the PSA recommends
103
+ sig { params(version: Dependabot::Julia::Version).returns(String) }
104
+ def stdlib_version_spec(version)
105
+ major = (version.segments[0] || 0).to_i
106
+ minor = (version.segments[1] || 0).to_i
107
+ patch = (version.segments[2] || 0).to_i
108
+
109
+ return "< 0.0.1" if major.zero? && minor.zero? && patch.zero?
110
+ return major.to_s if major.positive? && minor.zero?
111
+
112
+ simplified_version_spec(version)
113
+ end
114
+
54
115
  sig do
55
116
  params(
56
117
  requirement: Dependabot::DependencyRequirement,
@@ -97,8 +158,11 @@ module Dependabot
97
158
  # Append a new requirement that includes the target version
98
159
  # Following CompatHelper.jl's approach: use major.minor for versions >= 1.0,
99
160
  # 0.minor for 0.x versions, and 0.0.patch for 0.0.x versions
100
- new_spec = simplified_version_spec(target_version)
161
+ append_spec_string(requirement_string, simplified_version_spec(target_version))
162
+ end
101
163
 
164
+ sig { params(requirement_string: String, new_spec: String).returns(String) }
165
+ def append_spec_string(requirement_string, new_spec)
102
166
  # Append the new spec to the existing requirement (CompatHelper KeepEntry behavior)
103
167
  # Detect whether the existing requirement uses spaces after commas and preserve that format
104
168
  # and default to ", " if no commas found
@@ -49,6 +49,10 @@ module Dependabot
49
49
 
50
50
  sig { override.returns(T.nilable(Gem::Version)) }
51
51
  def latest_version
52
+ # A standard library has no registry target: Julia pins the version,
53
+ # only its compat entry is maintained (see #updated_requirements)
54
+ return nil if stdlib?
55
+
52
56
  @latest_version ||= T.let(latest_version_finder.latest_version, T.nilable(Gem::Version))
53
57
  end
54
58
 
@@ -77,6 +81,8 @@ module Dependabot
77
81
 
78
82
  sig { override.returns(T.nilable(T.any(Dependabot::Version, String))) }
79
83
  def latest_resolvable_version_with_no_unlock
84
+ return nil if stdlib?
85
+
80
86
  # Return the highest available version that satisfies the current
81
87
  # requirement constraints. requirements_array applies Julia compat
82
88
  # semantics (implicit caret, tilde, hyphen ranges) and returns
@@ -102,12 +108,26 @@ module Dependabot
102
108
  Dependabot::Julia::RequirementsUpdater.new(
103
109
  requirements: dependency.requirements,
104
110
  target_version: latest_resolvable_version&.to_s,
105
- update_strategy: requirements_update_strategy&.to_s&.to_sym
111
+ update_strategy: requirements_update_strategy&.to_s&.to_sym,
112
+ stdlib_versions: stdlib_versions
106
113
  ).updated_requirements
107
114
  end
108
115
 
109
116
  private
110
117
 
118
+ # Versions a stdlib's compat entry has to admit, by project file; set by
119
+ # the file parser for packages that ship with any Julia release the
120
+ # project supports
121
+ sig { returns(T::Hash[String, T::Array[String]]) }
122
+ def stdlib_versions
123
+ T.cast(dependency.metadata[:julia_stdlib_versions], T.nilable(T::Hash[String, T::Array[String]])) || {}
124
+ end
125
+
126
+ sig { returns(T::Boolean) }
127
+ def stdlib?
128
+ stdlib_versions.any?
129
+ end
130
+
111
131
  sig { returns(Dependabot::Julia::LatestVersionFinder) }
112
132
  def latest_version_finder
113
133
  @latest_version_finder ||= T.let(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-julia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.395.0
4
+ version: 0.397.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.395.0
18
+ version: 0.397.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.395.0
25
+ version: 0.397.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -262,7 +262,7 @@ licenses:
262
262
  - MIT
263
263
  metadata:
264
264
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
265
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.395.0
265
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.397.0
266
266
  rdoc_options: []
267
267
  require_paths:
268
268
  - lib