Skip to content

Commit cf4b25b

Browse files
Merge branch 'main' into harry/add-support-callable-workflows
2 parents 0d00d60 + 2cb11de commit cf4b25b

File tree

80 files changed

+11591
-396
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+11591
-396
lines changed

.github/copilot-instructions.md

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,65 @@ bundle exec srb tc path/to/file.rb
159159
bundle exec srb tc -a path/to/file.rb
160160
```
161161

162-
**Important**: Sorbet's autocorrect feature (`-a` flag) should be used cautiously as it can cause more issues than it resolves. Only use autocorrect when you have high confidence that the changes will not break code functionality.
162+
**Important**: Sorbet's autocorrect feature (`-a` flag) should be used cautiously as it can cause more issues than it resolves. Only use autocorrect when you have high confidence that the changes will not break code functionality.
163163

164164
Autocorrect can handle some simple cases like:
165-
- Adding missing `override.` annotations for method overrides
165+
- Adding missing `override.` annotations for method overrides
166166
- Adding `T.let` declarations for instance variables in strict-typed files
167167
- Adding type annotations for constants
168168

169169
However, autocorrect often creates incorrect fixes for complex type mismatches, method signature issues, and structural problems. **Always manually resolve Sorbet errors** rather than relying on autocorrect, and carefully review any autocorrected changes to ensure they maintain code correctness and intent.
170170

171+
### Code Comments and Documentation
172+
173+
**Prioritize self-documenting code over comments**. Write clear, intention-revealing code with descriptive method and variable names that eliminate the need for explanatory comments.
174+
175+
**When to use comments**:
176+
- **Business logic context**: Explain *why* something is done when the reason isn't obvious from the code
177+
- **Complex algorithms**: Document the approach or mathematical concepts
178+
- **Workarounds**: Explain why a non-obvious solution was necessary
179+
- **External constraints**: Document API limitations, system requirements, or ecosystem-specific behaviors
180+
- **TODO/FIXME**: Temporary markers for future improvements (with issue references when possible)
181+
182+
**Avoid these comment types**:
183+
- **Implementation decisions**: Don't explain what was *not* implemented or alternative approaches considered
184+
- **Obvious code explanations**: Don't restate what the code clearly does
185+
- **Apologies or justifications**: Comments defending coding choices suggest code quality issues
186+
- **Outdated information**: Remove comments that no longer apply to current implementation
187+
- **Version history**: Use git history instead of inline change logs
188+
189+
**Comment style guidelines**:
190+
```ruby
191+
# Good: Explains WHY, adds business context
192+
# Retry failed requests up to 3 times due to GitHub API rate limiting
193+
retry_count = 3
194+
195+
# Bad: Explains WHAT the code does (obvious from code)
196+
# Set retry count to 3
197+
retry_count = 3
198+
199+
# Good: Documents external constraint
200+
# GitHub API requires User-Agent header or returns 403
201+
headers['User-Agent'] = 'Dependabot/1.0'
202+
203+
# Bad: Implementation decision discussion
204+
# We decided not to cache this because it would complicate the code
205+
# and other ecosystems don't do caching here either
206+
response = fetch_data(url)
207+
```
208+
209+
**Prefer code refactoring over explanatory comments**:
210+
```ruby
211+
# Instead of commenting complex logic:
212+
# Calculate the SHA256 of downloaded file for security verification
213+
digest = Digest::SHA256.hexdigest(response.body)
214+
215+
# Extract to a well-named method:
216+
def calculate_security_checksum(content)
217+
Digest::SHA256.hexdigest(content)
218+
end
219+
```
220+
171221
### Native Helpers
172222

173223
Many ecosystems use native language helpers (Go, Node.js, Python) located in `{ecosystem}/helpers/`. These helpers run exclusively within containers and changes require rebuilding:
@@ -263,6 +313,62 @@ bin/dry-run.rb {ecosystem} {repo} --profile
263313

264314
When implementing new ecosystems or modifying existing ones, always ensure the 7 core classes are implemented and follow the established inheritance patterns from `dependabot-common`.
265315

316+
## Core Class Structure Pattern
317+
318+
**CRITICAL**: All Dependabot core classes with nested helper classes must follow the exact pattern to avoid "superclass mismatch" errors. This pattern is used consistently across all established ecosystems (bundler, npm_and_yarn, go_modules, etc.).
319+
320+
### Main Class Structure (applies to FileFetcher, FileParser, FileUpdater, UpdateChecker, etc.)
321+
```ruby
322+
# {ecosystem}/lib/dependabot/{ecosystem}/file_updater.rb (or file_fetcher.rb, file_parser.rb, etc.)
323+
require "dependabot/file_updaters"
324+
require "dependabot/file_updaters/base"
325+
326+
module Dependabot
327+
module {Ecosystem}
328+
class FileUpdater < Dependabot::FileUpdaters::Base
329+
# require_relative statements go INSIDE the class
330+
require_relative "file_updater/helper_class"
331+
332+
# Main logic here...
333+
end
334+
end
335+
end
336+
337+
Dependabot::FileUpdaters.register("{ecosystem}", Dependabot::{Ecosystem}::FileUpdater)
338+
```
339+
340+
### Helper Class Structure
341+
```ruby
342+
# {ecosystem}/lib/dependabot/{ecosystem}/file_updater/helper_class.rb
343+
require "dependabot/{ecosystem}/file_updater"
344+
345+
module Dependabot
346+
module {Ecosystem}
347+
class FileUpdater < Dependabot::FileUpdaters::Base
348+
class HelperClass
349+
# Helper logic nested INSIDE the main class
350+
end
351+
end
352+
end
353+
end
354+
```
355+
356+
### Key Rules:
357+
1. **Main classes** inherit from appropriate base: `Dependabot::FileUpdaters::Base`, `Dependabot::FileFetchers::Base`, etc.
358+
2. **Helper classes** are nested inside the main class
359+
3. **require_relative** statements go INSIDE the main class, not at module level
360+
4. **Helper classes require the main file** first: `require "dependabot/{ecosystem}/file_updater"`
361+
5. **Never define multiple top-level classes** with same name in the same namespace
362+
6. **Backward compatibility** can use static methods that delegate to instance methods
363+
364+
### Applies To:
365+
- **FileFetcher** and its helpers (e.g., `FileFetcher::GitCommitChecker`)
366+
- **FileParser** and its helpers (e.g., `FileParser::ManifestParser`)
367+
- **FileUpdater** and its helpers (e.g., `FileUpdater::LockfileUpdater`)
368+
- **UpdateChecker** and its helpers (e.g., `UpdateChecker::VersionResolver`)
369+
- **MetadataFinder** and its helpers
370+
- **Version** and **Requirement** classes (if they have nested classes)
371+
266372
## Adding New Ecosystems
267373

268374
If you are adding a new ecosystem, follow the detailed guide in `./NEW_ECOSYSTEMS.md` which provides step-by-step instructions for implementing a new package manager ecosystem.

bazel/lib/dependabot/bazel.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
# These all need to be required so the various classes can be registered in a
55
# lookup table of package manager names to concrete classes.
6-
require "dependabot/bazel/language"
7-
require "dependabot/bazel/package_manager"
86
require "dependabot/bazel/file_fetcher"
97
require "dependabot/bazel/file_parser"
108
require "dependabot/bazel/update_checker"
119
require "dependabot/bazel/file_updater"
10+
11+
require "dependabot/bazel/language"
12+
require "dependabot/bazel/package_manager"
1213
require "dependabot/bazel/metadata_finder"
1314
require "dependabot/bazel/version"
1415
require "dependabot/bazel/requirement"

common/lib/dependabot/dependency_graphers/base.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,17 @@ class Base
3131
sig { returns(T::Boolean) }
3232
attr_reader :prepared
3333

34+
sig { returns(T::Boolean) }
35+
attr_reader :errored_fetching_subdependencies
36+
3437
sig do
3538
params(file_parser: Dependabot::FileParsers::Base).void
3639
end
3740
def initialize(file_parser:)
3841
@file_parser = file_parser
3942
@dependencies = T.let([], T::Array[Dependabot::Dependency])
4043
@prepared = T.let(false, T::Boolean)
44+
@errored_fetching_subdependencies = T.let(false, T::Boolean)
4145
end
4246

4347
# Each grapher must implement a heuristic to determine which dependency file should be used as the owner
@@ -65,7 +69,7 @@ def resolved_dependencies
6569
package_url: build_purl(dep),
6670
direct: dep.top_level?,
6771
runtime: dep.production?,
68-
dependencies: fetch_subdependencies(dep)
72+
dependencies: safe_fetch_subdependencies(dep)
6973
)
7074
end
7175
end
@@ -80,6 +84,17 @@ def dependency_files
8084
file_parser.dependency_files
8185
end
8286

87+
sig { params(dependency: Dependabot::Dependency).returns(T::Array[String]) }
88+
def safe_fetch_subdependencies(dependency)
89+
return [] if @errored_fetching_subdependencies
90+
91+
fetch_subdependencies(dependency)
92+
rescue StandardError => e
93+
@errored_fetching_subdependencies = true
94+
Dependabot.logger.error("Error fetching subdependencies: #{e.message}")
95+
[]
96+
end
97+
8398
# Each grapher is expected to implement a method to look up the parents of a given dependency.
8499
#
85100
# The strategy that should be used is highly dependent on the ecosystem, in some cases the parser

common/lib/dependabot/file_updaters/base.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require "sorbet-runtime"
55

66
require "dependabot/credential"
7+
require "dependabot/notices"
78

89
module Dependabot
910
module FileUpdaters
@@ -52,6 +53,11 @@ def updated_dependency_files
5253
raise NotImplementedError
5354
end
5455

56+
sig { overridable.returns(T::Array[Dependabot::Notice]) }
57+
def notices
58+
[]
59+
end
60+
5561
private
5662

5763
sig { overridable.void }
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module Dependabot
5+
module Gradle
6+
module Distributions
7+
extend T::Sig
8+
9+
DISTRIBUTION_REPOSITORY_URL = "https://services.gradle.org"
10+
DISTRIBUTION_DEPENDENCY_TYPE = "gradle-distribution"
11+
12+
sig { params(requirements: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Boolean) }
13+
def self.distribution_requirements?(requirements)
14+
requirements.any? do |req|
15+
req.dig(:source, :type) == DISTRIBUTION_DEPENDENCY_TYPE
16+
end
17+
end
18+
end
19+
end
20+
end

gradle/lib/dependabot/gradle/file_fetcher.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ class FileFetcher < Dependabot::FileFetchers::Base
2424
SUPPORTED_SETTINGS_FILE_NAMES =
2525
T.let(%w(settings.gradle settings.gradle.kts).freeze, T::Array[String])
2626

27+
SUPPORTED_WRAPPER_FILES_PATH = %w(
28+
gradlew
29+
gradlew.bat
30+
gradle/wrapper/gradle-wrapper.jar
31+
gradle/wrapper/gradle-wrapper.properties
32+
).freeze
33+
2734
# For now Gradle only supports library .toml files in the main gradle folder
2835
SUPPORTED_VERSION_CATALOG_FILE_PATH =
2936
T.let(%w(/gradle/libs.versions.toml).freeze, T::Array[String])
@@ -76,6 +83,7 @@ def fetch_files
7683
def all_buildfiles_in_build(root_dir)
7784
files = [buildfile(root_dir), settings_file(root_dir), version_catalog_file(root_dir), lockfile(root_dir)]
7885
.compact
86+
files += wrapper_files(root_dir)
7987
files += subproject_buildfiles(root_dir)
8088
files += subproject_lockfiles(root_dir)
8189
files += dependency_script_plugins(root_dir)
@@ -172,6 +180,25 @@ def subproject_buildfiles(root_dir)
172180
end
173181
end
174182

183+
sig { params(dir: String).returns(T::Array[DependencyFile]) }
184+
def wrapper_files(dir)
185+
return [] unless Experiments.enabled?(:gradle_wrapper_updater)
186+
187+
SUPPORTED_WRAPPER_FILES_PATH.filter_map do |filename|
188+
file = fetch_file_if_present(File.join(dir, filename))
189+
next unless file
190+
191+
if file.name.end_with?(".jar")
192+
file.content = Base64.encode64(T.must(file.content)) if file.content
193+
file.content_encoding = DependencyFile::ContentEncoding::BASE64
194+
end
195+
file
196+
rescue Dependabot::DependencyFileNotFound
197+
# Gradle itself doesn't worry about missing subprojects, so we don't
198+
nil
199+
end
200+
end
201+
175202
sig { params(root_dir: String).returns(T.nilable(DependencyFile)) }
176203
def version_catalog_file(root_dir)
177204
return nil unless root_dir == "."

gradle/lib/dependabot/gradle/file_parser.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class FileParser < Dependabot::FileParsers::Base # rubocop:disable Metrics/Class
2525
extend T::Sig
2626

2727
require "dependabot/file_parsers/base/dependency_set"
28+
require_relative "file_parser/distributions_finder"
2829
require_relative "file_parser/property_value_finder"
2930

3031
SUPPORTED_BUILD_FILE_NAMES = T.let(
@@ -59,6 +60,11 @@ def parse
5960
script_plugin_files.each do |plugin_file|
6061
dependency_set += buildfile_dependencies(plugin_file)
6162
end
63+
if Experiments.enabled?(:gradle_wrapper_updater)
64+
wrapper_properties_file.each do |properties_file|
65+
dependency_set += wrapper_properties_dependencies(properties_file)
66+
end
67+
end
6268
version_catalog_file.each do |toml_file|
6369
dependency_set += version_catalog_dependencies(toml_file)
6470
end
@@ -119,6 +125,14 @@ def language
119125
)
120126
end
121127

128+
sig { params(properties_file: Dependabot::DependencyFile).returns(DependencySet) }
129+
def wrapper_properties_dependencies(properties_file)
130+
dependency_set = DependencySet.new
131+
dependency = DistributionsFinder.resolve_dependency(properties_file)
132+
dependency_set << dependency if dependency
133+
dependency_set
134+
end
135+
122136
sig { params(toml_file: Dependabot::DependencyFile).returns(DependencySet) }
123137
def version_catalog_dependencies(toml_file)
124138
dependency_set = DependencySet.new
@@ -544,6 +558,14 @@ def buildfiles
544558
)
545559
end
546560

561+
sig { returns(T::Array[Dependabot::DependencyFile]) }
562+
def wrapper_properties_file
563+
@wrapper_properties_file ||= T.let(
564+
dependency_files.select { |f| f.name.end_with?("gradle-wrapper.properties") },
565+
T.nilable(T::Array[Dependabot::DependencyFile])
566+
)
567+
end
568+
547569
sig { returns(T::Array[Dependabot::DependencyFile]) }
548570
def version_catalog_file
549571
@version_catalog_file ||= T.let(

0 commit comments

Comments
 (0)