Skip to content

Commit

Permalink
Strict type go_modules
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMagee committed Jun 29, 2024
1 parent 1e0a6e2 commit aa5b821
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 60 deletions.
3 changes: 3 additions & 0 deletions go_modules/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
inherit_from: ../.rubocop.yml

Sorbet/StrictSigil:
Enabled: true
63 changes: 46 additions & 17 deletions go_modules/lib/dependabot/go_modules/file_updater.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# typed: true
# typed: strong
# frozen_string_literal: true

require "sorbet-runtime"

require "dependabot/shared_helpers"
require "dependabot/file_updaters"
require "dependabot/file_updaters/base"
Expand All @@ -9,42 +11,56 @@
module Dependabot
module GoModules
class FileUpdater < Dependabot::FileUpdaters::Base
extend T::Sig

require_relative "file_updater/go_mod_updater"

def initialize(dependencies:, dependency_files:, repo_contents_path: nil,
credentials:, options: {})
sig do
override
.params(
dependencies: T::Array[Dependabot::Dependency],
dependency_files: T::Array[Dependabot::DependencyFile],
credentials: T::Array[Dependabot::Credential],
repo_contents_path: T.nilable(String),
options: T::Hash[Symbol, T.untyped]
)
.void
end
def initialize(dependencies:, dependency_files:, credentials:, repo_contents_path: nil, options: {})
super

@goprivate = options.fetch(:goprivate, "*")
@goprivate = T.let(options.fetch(:goprivate, "*"), String)
use_repo_contents_stub if repo_contents_path.nil?
end

sig { override.returns(T::Array[Regexp]) }
def self.updated_files_regex
[
/^go\.mod$/,
/^go\.sum$/
]
end

sig { override.returns(T::Array[Dependabot::DependencyFile]) }
def updated_dependency_files
updated_files = []

if go_mod && dependency_changed?(go_mod)
if go_mod && dependency_changed?(T.must(go_mod))
updated_files <<
updated_file(
file: go_mod,
content: file_updater.updated_go_mod_content
file: T.must(go_mod),
content: T.must(file_updater.updated_go_mod_content)
)

if go_sum && go_sum.content != file_updater.updated_go_sum_content
if go_sum && T.must(go_sum).content != file_updater.updated_go_sum_content
updated_files <<
updated_file(
file: go_sum,
content: file_updater.updated_go_sum_content
file: T.must(go_sum),
content: T.must(file_updater.updated_go_sum_content)
)
end

vendor_updater.updated_vendor_cache_files(base_directory: directory)
vendor_updater.updated_files(base_directory: T.must(directory))
.each do |file|
updated_files << file
end
Expand All @@ -57,19 +73,22 @@ def updated_dependency_files

private

sig { params(go_mod: Dependabot::DependencyFile).returns(T::Boolean) }
def dependency_changed?(go_mod)
# file_changed? only checks for changed requirements. Need to check for indirect dep version changes too.
file_changed?(go_mod) || dependencies.any? { |dep| dep.previous_version != dep.version }
end

sig { override.void }
def check_required_files
return if go_mod

raise "No go.mod!"
end

sig { returns(String) }
def use_repo_contents_stub
@repo_contents_stub = true
@repo_contents_stub = T.let(true, T.nilable(T::Boolean))
@repo_contents_path = Dir.mktmpdir

Dir.chdir(@repo_contents_path) do
Expand All @@ -92,45 +111,55 @@ def use_repo_contents_stub
end
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def go_mod
@go_mod ||= get_original_file("go.mod")
@go_mod ||= T.let(get_original_file("go.mod"), T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def go_sum
@go_sum ||= get_original_file("go.sum")
@go_sum ||= T.let(get_original_file("go.sum"), T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(String)) }
def directory
dependency_files.first&.directory
end

sig { returns(String) }
def vendor_dir
File.join(repo_contents_path, directory, "vendor")
end

sig { returns(Dependabot::FileUpdaters::VendorUpdater) }
def vendor_updater
Dependabot::FileUpdaters::VendorUpdater.new(
repo_contents_path: repo_contents_path,
vendor_dir: vendor_dir
)
end

sig { returns(GoModUpdater) }
def file_updater
@file_updater ||=
@file_updater ||= T.let(
GoModUpdater.new(
dependencies: dependencies,
dependency_files: dependency_files,
credentials: credentials,
repo_contents_path: repo_contents_path,
directory: directory,
directory: T.must(directory),
options: { tidy: tidy?, vendor: vendor?, goprivate: @goprivate }
)
),
T.nilable(Dependabot::GoModules::FileUpdater::GoModUpdater)
)
end

sig { returns(T::Boolean) }
def tidy?
!@repo_contents_stub
end

sig { returns(T::Boolean) }
def vendor?
File.exist?(File.join(vendor_dir, "modules.txt"))
end
Expand Down
2 changes: 1 addition & 1 deletion go_modules/lib/dependabot/go_modules/metadata_finder.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: strict
# typed: strong
# frozen_string_literal: true

require "sorbet-runtime"
Expand Down
9 changes: 8 additions & 1 deletion go_modules/lib/dependabot/go_modules/native_helpers.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
# typed: true
# typed: strong
# frozen_string_literal: true

require "sorbet-runtime"

module Dependabot
module GoModules
module NativeHelpers
extend T::Sig

sig { returns(String) }
def self.helper_path
clean_path(File.join(native_helpers_root, "go_modules/bin/helper"))
end

sig { returns(String) }
def self.native_helpers_root
default_path = File.join(__dir__, "../../../helpers/install-dir")
ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
end

sig { params(path: String).returns(String) }
def self.clean_path(path)
Pathname.new(path).cleanpath.to_path
end
Expand Down
23 changes: 18 additions & 5 deletions go_modules/lib/dependabot/go_modules/path_converter.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "sorbet-runtime"

require "dependabot/go_modules/native_helpers"

module Dependabot
module GoModules
module PathConverter
extend T::Sig

sig do
params(path: T.untyped)
.returns(
T.nilable(String)
)
end
def self.git_url_for_path(path)
# Save a query by manually converting golang.org/x names
import_path = path.gsub(%r{^golang\.org/x}, "github.com/golang")

SharedHelpers.run_helper_subprocess(
command: NativeHelpers.helper_path,
function: "getVcsRemoteForImport",
args: { import: import_path }
T.cast(
SharedHelpers.run_helper_subprocess(
command: NativeHelpers.helper_path,
function: "getVcsRemoteForImport",
args: { import: import_path }
),
T.nilable(String)
)
end
end
Expand Down
10 changes: 8 additions & 2 deletions go_modules/lib/dependabot/go_modules/resolvability_errors.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# typed: true
# typed: strong
# frozen_string_literal: true

require "sorbet-runtime"

module Dependabot
module GoModules
module ResolvabilityErrors
extend T::Sig

GITHUB_REPO_REGEX = %r{github.com/[^:@]*}

sig { params(message: String, goprivate: T.untyped).void }
def self.handle(message, goprivate:)
mod_path = message.scan(GITHUB_REPO_REGEX).last
unless mod_path && message.include?("If this is a private repository")
Expand All @@ -17,9 +22,10 @@ def self.handle(message, goprivate:)
SharedHelpers.in_a_temporary_directory do
File.write("go.mod", "module dummy\n")

mod_path = T.cast(mod_path, String)
mod_split = mod_path.split("/")
repo_path = if mod_split.size > 3
mod_split[0..2].join("/")
T.must(mod_split[0..2]).join("/")
else
mod_path
end
Expand Down
24 changes: 21 additions & 3 deletions go_modules/lib/dependabot/go_modules/update_checker.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "sorbet-runtime"

require "dependabot/update_checkers"
require "dependabot/update_checkers/base"
require "dependabot/shared_helpers"
Expand All @@ -10,34 +12,42 @@
module Dependabot
module GoModules
class UpdateChecker < Dependabot::UpdateCheckers::Base
extend T::Sig

require_relative "update_checker/latest_version_finder"

sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
def latest_resolvable_version
latest_version_finder.latest_version
end

# This is currently used to short-circuit latest_resolvable_version,
# with the assumption that it'll be quicker than checking
# resolvability. As this is quite quick in Go anyway, we just alias.
sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
def latest_version
latest_resolvable_version
end

sig { override.returns(T.nilable(Dependabot::Version)) }
def lowest_resolvable_security_fix_version
raise "Dependency not vulnerable!" unless vulnerable?

lowest_security_fix_version
end

sig { override.returns(Dependabot::Version) }
def lowest_security_fix_version
latest_version_finder.lowest_security_fix_version
end

sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
def latest_resolvable_version_with_no_unlock
# Irrelevant, since Go modules uses a single dependency file
nil
end

sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def updated_requirements
dependency.requirements.map do |req|
req.merge(requirement: latest_version)
Expand All @@ -46,8 +56,9 @@ def updated_requirements

private

sig { returns(Dependabot::GoModules::UpdateChecker::LatestVersionFinder) }
def latest_version_finder
@latest_version_finder ||=
@latest_version_finder ||= T.let(
LatestVersionFinder.new(
dependency: dependency,
dependency_files: dependency_files,
Expand All @@ -56,23 +67,29 @@ def latest_version_finder
security_advisories: security_advisories,
raise_on_ignored: raise_on_ignored,
goprivate: options.fetch(:goprivate, "*")
)
),
T.nilable(Dependabot::GoModules::UpdateChecker::LatestVersionFinder)
)
end

sig { override.returns(T::Boolean) }
def latest_version_resolvable_with_full_unlock?
# Full unlock checks aren't implemented for Go (yet)
false
end

sig { override.returns(T::Array[Dependabot::Dependency]) }
def updated_dependencies_after_full_unlock
raise NotImplementedError
end

# Go only supports semver and semver-compliant pseudo-versions, so it can't be a SHA.
sig { returns(T::Boolean) }
def existing_version_is_sha?
false
end

sig { params(tag: T.nilable(T::Hash[Symbol, String])).returns(T.untyped) }
def version_from_tag(tag)
# To compare with the current version we either use the commit SHA
# (if that's what the parser picked up) or the tag name.
Expand All @@ -81,6 +98,7 @@ def version_from_tag(tag)
tag&.fetch(:tag)
end

sig { returns(T::Hash[Symbol, T.untyped]) }
def default_source
{ type: "default", source: dependency.name }
end
Expand Down
Loading

0 comments on commit aa5b821

Please sign in to comment.