Skip to content

Commit

Permalink
Merge branch 'dependabot:main' into settings-gradle-kts
Browse files Browse the repository at this point in the history
anatawa12 authored Nov 18, 2021
2 parents 3805a01 + 5db0e3f commit e1411a7
Showing 37 changed files with 670 additions and 27 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
## v0.167.0, 16 November 2021

- Maven: Correctly handle nested declarations [#4417](https://github.com/dependabot/dependabot-core/pull/4417)
- Add parsing of MSBuild SDK dependencies (NuGet) (@Zastai) [#2849](https://github.com/dependabot/dependabot-core/pull/2849)
- fix: remove fixed error message check [#4412](https://github.com/dependabot/dependabot-core/pull/4412)

## v0.166.1, 12 November 2021

- Terraform: Improve file updater and registry request error handling [#4405](https://github.com/dependabot/dependabot-core/pull/4405)
- Explicitly ignore metadata detection for fuchsia.googlesource.com [#4402](https://github.com/dependabot/dependabot-core/pull/4402)

## v0.166.0, 11 November 2021

- Ignore errors from Source enterprise check and ignore known failures [#4401](https://github.com/dependabot/dependabot-core/pull/4401)
- Bump to go 1.17.3 (@jeffwidman) [#4393](https://github.com/dependabot/dependabot-core/pull/4393)
- Move composer-not-found fixture from decommissioned dependabot.com [#4399](https://github.com/dependabot/dependabot-core/pull/4399)

## v0.165.0, 8 November 2021

- Add timeout per operation [#4362](https://github.com/dependabot/dependabot-core/pull/4362)

## v0.164.1, 2 November 2021

- Only check auth for github.com when running `bump-version` [#4347](https://github.com/dependabot/dependabot-core/pull/4347)
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -167,8 +167,8 @@ USER root
### GO

# Install Go
ARG GOLANG_VERSION=1.17.1
ARG GOLANG_CHECKSUM=dab7d9c34361dc21ec237d584590d72500652e7c909bf082758fb63064fca0ef
ARG GOLANG_VERSION=1.17.3
ARG GOLANG_CHECKSUM=550f9845451c0c94be679faf116291e7807a8d78b43149f9506c1b15eb89008c
ENV PATH=/opt/go/bin:$PATH
RUN cd /tmp \
&& curl --http1.1 -o go.tar.gz https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz \
5 changes: 5 additions & 0 deletions bundler/helpers/v1/run.rb
Original file line number Diff line number Diff line change
@@ -6,6 +6,11 @@
$LOAD_PATH.unshift(File.expand_path("./lib", __dir__))
$LOAD_PATH.unshift(File.expand_path("./monkey_patches", __dir__))

trap "HUP" do
puts JSON.generate(error: "timeout", error_class: "Timeout::Error", trace: [])
exit 2
end

# Bundler monkey patches
require "definition_ruby_version_patch"
require "definition_bundler_version_patch"
5 changes: 5 additions & 0 deletions bundler/helpers/v2/run.rb
Original file line number Diff line number Diff line change
@@ -6,6 +6,11 @@
$LOAD_PATH.unshift(File.expand_path("./lib", __dir__))
$LOAD_PATH.unshift(File.expand_path("./monkey_patches", __dir__))

trap "HUP" do
puts JSON.generate(error: "timeout", error_class: "Timeout::Error", trace: [])
exit 2
end

# Bundler monkey patches
require "definition_ruby_version_patch"
require "definition_bundler_version_patch"
2 changes: 2 additions & 0 deletions bundler/lib/dependabot/bundler/file_parser.rb
Original file line number Diff line number Diff line change
@@ -145,6 +145,7 @@ def parsed_gemfile
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "parsed_gemfile",
options: options,
args: {
gemfile_name: gemfile.name,
lockfile_name: lockfile&.name,
@@ -175,6 +176,7 @@ def parsed_gemspec(file)
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "parsed_gemspec",
options: options,
args: {
gemspec_name: file.name,
lockfile_name: lockfile&.name,
1 change: 1 addition & 0 deletions bundler/lib/dependabot/bundler/file_updater.rb
Original file line number Diff line number Diff line change
@@ -79,6 +79,7 @@ def vendor_cache_dir
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "vendor_cache_dir",
options: options,
args: {
dir: repo_contents_path
}
Original file line number Diff line number Diff line change
@@ -69,6 +69,7 @@ def build_updated_lockfile
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "update_lockfile",
options: options,
args: {
gemfile_name: gemfile.name,
lockfile_name: lockfile.name,
46 changes: 36 additions & 10 deletions bundler/lib/dependabot/bundler/native_helpers.rb
Original file line number Diff line number Diff line change
@@ -6,20 +6,51 @@
module Dependabot
module Bundler
module NativeHelpers
def self.run_bundler_subprocess(function:, args:, bundler_version:)
class BundleCommand
MAX_SECONDS = 1800
MIN_SECONDS = 60

def initialize(timeout_seconds)
@timeout_seconds = clamp(timeout_seconds)
end

def build(script)
[timeout_command, :bundle, :exec, :ruby, script].compact.join(" ")
end

private

attr_reader :timeout_seconds

def timeout_command
"timeout -s HUP #{timeout_seconds}" unless timeout_seconds.zero?
end

def clamp(seconds)
return 0 unless seconds

seconds.to_i.clamp(MIN_SECONDS, MAX_SECONDS)
end
end

def self.run_bundler_subprocess(function:, args:, bundler_version:, options: {})
# Run helper suprocess with all bundler-related ENV variables removed
bundler_major_version = bundler_version.split(".").first
helpers_path = versioned_helper_path(bundler_version: bundler_major_version)
::Bundler.with_original_env do
command = BundleCommand.
new(options[:timeout_per_operation_seconds]).
build(File.join(helpers_path, "run.rb"))
SharedHelpers.run_helper_subprocess(
command: helper_path(bundler_version: bundler_major_version),
command: command,
function: function,
args: args,
env: {
# Bundler will pick the matching installed major version
"BUNDLER_VERSION" => bundler_version,
"BUNDLE_GEMFILE" => File.join(versioned_helper_path(bundler_version: bundler_major_version), "Gemfile"),
"BUNDLE_GEMFILE" => File.join(helpers_path, "Gemfile"),
# Prevent the GEM_HOME from being set to a folder owned by root
"GEM_HOME" => File.join(versioned_helper_path(bundler_version: bundler_major_version), ".bundle")
"GEM_HOME" => File.join(helpers_path, ".bundle")
}
)
rescue SharedHelpers::HelperSubprocessFailed => e
@@ -31,12 +62,7 @@ def self.run_bundler_subprocess(function:, args:, bundler_version:)
end

def self.versioned_helper_path(bundler_version:)
native_helper_version = "v#{bundler_version}"
File.join(native_helpers_root, native_helper_version)
end

def self.helper_path(bundler_version:)
"bundle exec ruby #{File.join(versioned_helper_path(bundler_version: bundler_version), 'run.rb')}"
File.join(native_helpers_root, "v#{bundler_version}")
end

def self.native_helpers_root
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ def conflicting_dependencies(dependency:, target_version:)
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "conflicting_dependencies",
options: options,
args: {
dir: tmp_dir,
dependency_name: dependency.name,
Original file line number Diff line number Diff line change
@@ -50,6 +50,7 @@ def force_update
updated_deps, specs = NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "force_update",
options: options,
args: {
dir: tmp_dir,
dependency_name: dependency.name,
Original file line number Diff line number Diff line change
@@ -61,6 +61,7 @@ def latest_git_version_details
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "depencency_source_latest_git_version",
options: options,
args: {
dir: tmp_dir,
gemfile_name: gemfile.name,
@@ -106,6 +107,7 @@ def private_registry_versions
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "private_registry_versions",
options: options,
args: {
dir: tmp_dir,
gemfile_name: gemfile.name,
@@ -126,6 +128,7 @@ def source_type
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "dependency_source_type",
options: options,
args: {
dir: tmp_dir,
gemfile_name: gemfile.name,
Original file line number Diff line number Diff line change
@@ -167,6 +167,7 @@ def inaccessible_git_dependencies
git_specs = NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "git_specs",
options: options,
args: {
dir: tmp_dir,
gemfile_name: gemfile.name,
@@ -195,6 +196,7 @@ def jfrog_source
NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "jfrog_source",
options: options,
args: {
dir: dir,
gemfile_name: gemfile.name,
Original file line number Diff line number Diff line change
@@ -82,6 +82,7 @@ def fetch_latest_resolvable_version_details
details = NativeHelpers.run_bundler_subprocess(
bundler_version: bundler_version,
function: "resolve_version",
options: options,
args: {
dependency_name: dependency.name,
dependency_requirements: dependency.requirements,
93 changes: 93 additions & 0 deletions bundler/spec/dependabot/bundler/native_helpers_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

require "spec_helper"
require "dependabot/bundler/native_helpers"

RSpec.describe Dependabot::Bundler::NativeHelpers do
subject { described_class }

describe ".run_bundler_subprocess" do
let(:options) { {} }

before do
allow(Dependabot::SharedHelpers).to receive(:run_helper_subprocess)

subject.run_bundler_subprocess(
function: "noop",
args: [],
bundler_version: "2.0.0",
options: options
)
end

context "with a timeout provided" do
let(:options) { { timeout_per_operation_seconds: 120 } }

it "terminates the spawned process when the timeout is exceeded" do
expect(Dependabot::SharedHelpers).
to have_received(:run_helper_subprocess).
with(
command: "timeout -s HUP 120 bundle exec ruby /opt/bundler/v2/run.rb",
function: "noop",
args: [],
env: anything
)
end
end

context "with a timeout that is too high" do
let(:thirty_minutes_plus_one_second) { 1801 }
let(:options) do
{
timeout_per_operation_seconds: thirty_minutes_plus_one_second
}
end

it "applies the maximum timeout" do
expect(Dependabot::SharedHelpers).
to have_received(:run_helper_subprocess).
with(
command: "timeout -s HUP 1800 bundle exec ruby /opt/bundler/v2/run.rb",
function: "noop",
args: [],
env: anything
)
end
end

context "with a timeout that is too low" do
let(:fifty_nine_seconds) { 59 }
let(:options) do
{
timeout_per_operation_seconds: fifty_nine_seconds
}
end

it "applies the minimum timeout" do
expect(Dependabot::SharedHelpers).
to have_received(:run_helper_subprocess).
with(
command: "timeout -s HUP 60 bundle exec ruby /opt/bundler/v2/run.rb",
function: "noop",
args: [],
env: anything
)
end
end

context "without a timeout" do
let(:options) { {} }

it "does not apply a timeout" do
expect(Dependabot::SharedHelpers).
to have_received(:run_helper_subprocess).
with(
command: "bundle exec ruby /opt/bundler/v2/run.rb",
function: "noop",
args: [],
env: anything
)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -253,6 +253,7 @@
with({
bundler_version: bundler_version,
function: "dependency_source_type",
options: anything,
args: anything
}).and_call_original

@@ -261,6 +262,7 @@
with({
bundler_version: bundler_version,
function: "private_registry_versions",
options: anything,
args: anything
}).
and_return(
@@ -308,6 +310,7 @@
with({
bundler_version: bundler_version,
function: "private_registry_versions",
options: anything,
args: anything
}).
and_raise(subprocess_error)
@@ -341,6 +344,7 @@
with({
bundler_version: bundler_version,
function: "private_registry_versions",
options: anything,
args: anything
}).
and_raise(subprocess_error)
@@ -374,6 +378,7 @@
with({
bundler_version: bundler_version,
function: "private_registry_versions",
options: anything,
args: anything
}).
and_raise(subprocess_error)
@@ -407,6 +412,7 @@
with({
bundler_version: bundler_version,
function: "private_registry_versions",
options: anything,
args: anything
}).
and_raise(subprocess_error)
@@ -429,6 +435,7 @@
with({
bundler_version: bundler_version,
function: "private_registry_versions",
options: anything,
args: anything
}).
and_return(
@@ -570,6 +577,7 @@
with({
bundler_version: bundler_version,
function: "dependency_source_type",
options: anything,
args: anything
}).and_call_original

@@ -578,6 +586,7 @@
with({
bundler_version: bundler_version,
function: "private_registry_versions",
options: anything,
args: anything
}).
and_return(
Original file line number Diff line number Diff line change
@@ -262,6 +262,7 @@
with({
bundler_version: PackageManagerHelper.bundler_version,
function: "resolve_version",
options: anything,
args: anything
}).
and_return(
2 changes: 2 additions & 0 deletions bundler/spec/dependabot/bundler/update_checker_spec.rb
Original file line number Diff line number Diff line change
@@ -158,6 +158,7 @@
with({
bundler_version: bundler_version,
function: "dependency_source_type",
options: anything,
args: anything
}).and_call_original

@@ -166,6 +167,7 @@
with({
bundler_version: bundler_version,
function: "private_registry_versions",
options: anything,
args: anything
}).
and_return(
Original file line number Diff line number Diff line change
@@ -57,9 +57,7 @@ def changelog_text

# If pandoc isn't installed just return the rst
pruned_text
rescue RuntimeError => e
raise unless e.message.include?("Pandoc timed out")

rescue RuntimeError
pruned_text
end
end
5 changes: 5 additions & 0 deletions common/lib/dependabot/source.rb
Original file line number Diff line number Diff line change
@@ -45,6 +45,8 @@ class Source
(?:#{AZURE_SOURCE})
/x.freeze

IGNORED_PROVIDER_HOSTS = %w(gitbox.apache.org svn.apache.org fuchsia.googlesource.com).freeze

attr_accessor :provider, :repo, :directory, :branch, :commit,
:hostname, :api_endpoint

@@ -64,6 +66,7 @@ def self.from_url(url_string)
def self.github_enterprise_from_url(url_string)
captures = url_string&.match(GITHUB_ENTERPRISE_SOURCE)&.named_captures
return unless captures
return if IGNORED_PROVIDER_HOSTS.include?(captures.fetch("host"))

base_url = "https://#{captures.fetch('host')}"

@@ -86,6 +89,8 @@ def self.github_enterprise?(base_url)
# currently doesn't work with development environments
resp.headers["X-GitHub-Request-Id"] &&
!resp.headers["X-GitHub-Request-Id"].empty?
rescue Excon::Error
false
end

def initialize(provider:, repo:, directory: nil, branch: nil, commit: nil,
2 changes: 1 addition & 1 deletion common/lib/dependabot/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Dependabot
VERSION = "0.164.1"
VERSION = "0.167.0"
end
5 changes: 5 additions & 0 deletions common/spec/dependabot/source_spec.rb
Original file line number Diff line number Diff line change
@@ -235,6 +235,11 @@
end
end

context "with an explicitly ignored URL" do
let(:url) { "https://gitbox.apache.org/repos/asf?p=commons-lang.git" }
it { is_expected.to be_nil }
end

context "with a Bitbucket URL" do
let(:url) do
"https://bitbucket.org/org/abc/src/master/dir/readme.md?at=default"
Original file line number Diff line number Diff line change
@@ -161,7 +161,7 @@
expect { resolver.latest_resolvable_version }.
to raise_error(Dependabot::DependencyFileNotResolvable) do |error|
expect(error.message).to start_with(
'The "https://dependabot.com/composer-not-found/packages.json"'\
'The "https://github.com/dependabot/composer-not-found/packages.json"'\
" file could not be downloaded"
)
end
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
"repositories": [
{
"type": "composer",
"url": "https://dependabot.com/composer-not-found"
"url": "https://github.com/dependabot/composer-not-found"
}
],
"require": {
13 changes: 8 additions & 5 deletions maven/lib/dependabot/maven/file_updater/declaration_finder.rb
Original file line number Diff line number Diff line change
@@ -9,9 +9,7 @@ module Dependabot
module Maven
class FileUpdater
class DeclarationFinder
DECLARATION_REGEX =
%r{<parent>.*?</parent>|<dependency>.*?</dependency>|
<plugin>.*?</plugin>|<extension>.*?</extension>}mx.freeze
DECLARATION_TYPES = %w(parent dependency plugin extension).freeze

attr_reader :dependency, :declaring_requirement, :dependency_files

@@ -78,9 +76,14 @@ def node_group_id(node)
end

def deep_find_declarations(string)
string.scan(DECLARATION_REGEX).flat_map do |matching_node|
[matching_node, *deep_find_declarations(matching_node[1..-1])]
pom = Nokogiri::XML(string)
nodes = []
pom.traverse do |node|
next unless DECLARATION_TYPES.include?(node.node_name)

nodes << node.to_s
end
nodes
end

def declaring_requirement_matches?(node)
Original file line number Diff line number Diff line change
@@ -105,7 +105,7 @@ def version_string(dep)
dependency: dep,
declaring_requirement: declaring_requirement,
dependency_files: dependency_files
).declaration_nodes.first.at_css("version")&.content
).declaration_nodes.first.at_xpath("./*/version")&.content
end

def pom
Original file line number Diff line number Diff line change
@@ -420,5 +420,32 @@
to eq("org.springframework")
end
end

context "with a plugin that contains a nested plugin configuration declaration" do
let(:pom) do
Dependabot::DependencyFile.new(name: "pom.xml", content: fixture("poms", "nested_plugin.xml"))
end
let(:dependency_name) { "org.jetbrains.kotlin:kotlin-maven-plugin" }
let(:dependency_version) { "1.4.30" }
let(:declaring_requirement) do
{
requirement: dependency_version,
file: "pom.xml",
groups: [],
source: nil,
metadata: { packaging_type: "jar", property_name: "kotlin.version" }
}
end

it "finds the declaration" do
expect(declaration_nodes.count).to eq(1)

declaration_node = declaration_nodes.first
expect(declaration_node).to be_a(Nokogiri::XML::Node)
expect(declaration_node.at_xpath("./*/version").content).to eq("${kotlin.version}")
expect(declaration_node.at_xpath("./*/artifactId").content).to eq("kotlin-maven-plugin")
expect(declaration_node.at_xpath("./*/groupId").content).to eq("org.jetbrains.kotlin")
end
end
end
end
81 changes: 81 additions & 0 deletions maven/spec/fixtures/poms/nested_plugin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.dependabot</groupId>
<artifactId>basic-pom</artifactId>
<version>0.0.1-RELEASE</version>
<name>Dependabot Basic POM</name>

<properties>
<kotlin.version>1.4.30</kotlin.version>
</properties>

<packaging>pom</packaging>

<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.3-jre</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>1.0.0</version>
<classifier>sources</classifier>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<configuration>
<jvmTarget>11</jvmTarget>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
63 changes: 61 additions & 2 deletions nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ class ProjectFileParser
"ItemGroup > Dependency, "\
"ItemGroup > DevelopmentDependency"

PROJECT_SDK_REGEX = %r{^([^/]+)/(\d+(?:[.]\d+(?:[.]\d+)?)?(?:[+-].*)?)$}.freeze
PROPERTY_REGEX = /\$\((?<property>.*?)\)/.freeze
ITEM_REGEX = /\@\((?<property>.*?)\)/.freeze

@@ -32,16 +33,19 @@ def dependency_set(project_file:)

doc = Nokogiri::XML(project_file.content)
doc.remove_namespaces!
# Look for regular package references
doc.css(DEPENDENCY_SELECTOR).each do |dependency_node|
name = dependency_name(dependency_node, project_file)
req = dependency_requirement(dependency_node, project_file)
version = dependency_version(dependency_node, project_file)
prop_name = req_property_name(dependency_node)

dependency =
build_dependency(name, req, version, prop_name, project_file)
dependency = build_dependency(name, req, version, prop_name, project_file)
dependency_set << dependency if dependency
end
# Look for SDK references; see:
# https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk
add_sdk_references(doc, dependency_set, project_file)

dependency_set
end
@@ -50,6 +54,61 @@ def dependency_set(project_file:)

attr_reader :dependency_files

def add_sdk_references(doc, dependency_set, project_file)
# These come in 3 flavours:
# - <Project Sdk="Name/Version">
# - <Sdk Name="Name" Version="Version" />
# - <Import Project="..." Sdk="Name" Version="Version" />
# None of these support the use of properties, nor do they allow child
# elements instead of attributes.
add_sdk_refs_from_project(doc, dependency_set, project_file)
add_sdk_refs_from_sdk_tags(doc, dependency_set, project_file)
add_sdk_refs_from_import_tags(doc, dependency_set, project_file)
end

def add_sdk_ref_from_project(sdk_references, dependency_set, project_file)
sdk_references.split(";")&.each do |sdk_reference|
m = sdk_reference.match(PROJECT_SDK_REGEX)
if m
dependency = build_dependency(m[1], m[2], m[2], nil, project_file)
dependency_set << dependency if dependency
end
end
end

def add_sdk_refs_from_import_tags(doc, dependency_set, project_file)
doc.xpath("/Project/Import").each do |import_node|
next unless import_node.attribute("Sdk") && import_node.attribute("Version")

name = import_node.attribute("Sdk")&.value&.strip
version = import_node.attribute("Version")&.value&.strip

dependency = build_dependency(name, version, version, nil, project_file)
dependency_set << dependency if dependency
end
end

def add_sdk_refs_from_project(doc, dependency_set, project_file)
doc.xpath("/Project").each do |project_node|
sdk_references = project_node.attribute("Sdk")&.value&.strip
next unless sdk_references

add_sdk_ref_from_project(sdk_references, dependency_set, project_file)
end
end

def add_sdk_refs_from_sdk_tags(doc, dependency_set, project_file)
doc.xpath("/Project/Sdk").each do |sdk_node|
next unless sdk_node.attribute("Version")

name = sdk_node.attribute("Name")&.value&.strip
version = sdk_node.attribute("Version")&.value&.strip

dependency = build_dependency(name, version, version, nil, project_file)
dependency_set << dependency if dependency
end
end

def build_dependency(name, req, version, prop_name, project_file)
return unless name

Original file line number Diff line number Diff line change
@@ -20,6 +20,17 @@ class ProjectFileDeclarationFinder
<DevelopmentDependency [^>]*?/>|
<DevelopmentDependency [^>]*?[^/]>.*?</DevelopmentDependency>
}mx.freeze
SDK_IMPORT_REGEX =
/ <Import [^>]*?Sdk="[^"]*?"[^>]*?Version="[^"]*?"[^>]*?>
| <Import [^>]*?Version="[^"]*?"[^>]*?Sdk="[^"]*?"[^>]*?>
/mx.freeze
SDK_PROJECT_REGEX =
/ <Project [^>]*?Sdk="[^"]*?"[^>]*?>
/mx.freeze
SDK_SDK_REGEX =
/ <Sdk [^>]*?Name="[^"]*?"[^>]*?Version="[^"]*?"[^>]*?>
| <Sdk [^>]*?Version="[^"]*?"[^>]*?Name="[^"]*?"[^>]*?>
/mx.freeze

attr_reader :dependency_name, :declaring_requirement,
:dependency_files
@@ -33,6 +44,7 @@ def initialize(dependency_name:, dependency_files:,

def declaration_strings
@declaration_strings ||= fetch_declaration_strings
@declaration_strings += fetch_sdk_strings
end

def declaration_nodes
@@ -72,6 +84,10 @@ def fetch_declaration_strings
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity

def fetch_sdk_strings
sdk_project_strings + sdk_sdk_strings + sdk_import_strings
end

# rubocop:disable Metrics/PerceivedComplexity
def get_node_version_value(node)
attribute = "Version"
@@ -95,6 +111,71 @@ def declaring_file

raise "No file found with name #{filename}!"
end

def sdk_import_strings
sdk_strings(SDK_IMPORT_REGEX, "Import", "Sdk", "Version")
end

def parse_element(string, name)
xml = string
xml += "</#{name}>" unless string.end_with?("/>")
node = Nokogiri::XML(xml)
node.remove_namespaces!
node.at_xpath("/#{name}")
end

def get_attribute_value_nocase(element, name)
value = element.attribute(name)&.value ||
element.attribute(name.downcase)&.value ||
element.attribute(name.upcase)&.value
value&.strip
end

def desired_sdk_reference?(sdk_reference, dep_name, dep_version)
parts = sdk_reference.split("/")
parts.length == 2 && parts[0]&.downcase == dep_name && parts[1] == dep_version
end

def sdk_project_strings
dep_name = dependency_name&.downcase
dep_version = declaring_requirement.fetch(:requirement)
strings = []
declaring_file.content.scan(SDK_PROJECT_REGEX).each do |string|
element = parse_element(string, "Project")
next unless element

sdk_references = get_attribute_value_nocase(element, "Sdk")
next unless sdk_references&.include?("/")

sdk_references.split(";").each do |sdk_reference|
strings << sdk_reference if desired_sdk_reference?(sdk_reference, dep_name, dep_version)
end
end
strings.uniq
end

def sdk_sdk_strings
sdk_strings(SDK_SDK_REGEX, "Sdk", "Name", "Version")
end

def sdk_strings(regex, element_name, name_attribute, version_attribute)
dep_name = dependency_name&.downcase
dep_version = declaring_requirement.fetch(:requirement)
strings = []
declaring_file.content.scan(regex).each do |string|
element = parse_element(string, element_name)
next unless element

node_name = get_attribute_value_nocase(element, name_attribute)&.downcase
next unless node_name == dep_name

node_version = get_attribute_value_nocase(element, version_attribute)
next unless node_version == dep_version

strings << string
end
strings
end
end
end
end
116 changes: 116 additions & 0 deletions nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb
Original file line number Diff line number Diff line change
@@ -244,6 +244,122 @@
expect(dependencies.count).to eq(0)
end
end

context "with a versioned sdk reference" do
context "specified in the Project tag" do
let(:file_body) { fixture("csproj", "sdk_reference_via_project.csproj") }

its(:length) { is_expected.to eq(2) }

describe "the first dependency" do
subject(:dependency) { dependencies.first }

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("Awesome.Sdk")
expect(dependency.version).to eq("1.2.3")
expect(dependency.requirements).to eq([{
requirement: "1.2.3",
file: "my.csproj",
groups: [],
source: nil
}])
end
end

describe "the second dependency" do
subject(:dependency) { dependencies[1] }

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("Prototype.Sdk")
expect(dependency.version).to eq("0.1.0-beta")
expect(dependency.requirements).to eq([{
requirement: "0.1.0-beta",
file: "my.csproj",
groups: [],
source: nil
}])
end
end
end

context "specified via an Sdk tag" do
let(:file_body) { fixture("csproj", "sdk_reference_via_sdk.csproj") }

its(:length) { is_expected.to eq(2) }

describe "the first dependency" do
subject(:dependency) { dependencies.first }

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("Awesome.Sdk")
expect(dependency.version).to eq("1.2.3")
expect(dependency.requirements).to eq([{
requirement: "1.2.3",
file: "my.csproj",
groups: [],
source: nil
}])
end
end

describe "the second dependency" do
subject(:dependency) { dependencies[1] }

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("Prototype.Sdk")
expect(dependency.version).to eq("0.1.0-beta")
expect(dependency.requirements).to eq([{
requirement: "0.1.0-beta",
file: "my.csproj",
groups: [],
source: nil
}])
end
end
end

context "specified via an Import tag" do
let(:file_body) { fixture("csproj", "sdk_reference_via_import.csproj") }

its(:length) { is_expected.to eq(2) }

describe "the first dependency" do
subject(:dependency) { dependencies.first }

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("Awesome.Sdk")
expect(dependency.version).to eq("1.2.3")
expect(dependency.requirements).to eq([{
requirement: "1.2.3",
file: "my.csproj",
groups: [],
source: nil
}])
end
end

describe "the second dependency" do
subject(:dependency) { dependencies[1] }

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("Prototype.Sdk")
expect(dependency.version).to eq("0.1.0-beta")
expect(dependency.requirements).to eq([{
requirement: "0.1.0-beta",
file: "my.csproj",
groups: [],
source: nil
}])
end
end
end
end
end
end
end
43 changes: 43 additions & 0 deletions nuget/spec/dependabot/nuget/file_updater_spec.rb
Original file line number Diff line number Diff line change
@@ -135,6 +135,49 @@
)
end
end

context "with MSBuild SDKs" do
let(:csproj_body) do
fixture("csproj", "sdk_references_of_all_kinds.csproj")
end
let(:dependency_name) { "Foo.Bar" }
let(:version) { "1.2.3" }
let(:previous_version) { "1.1.1" }
let(:requirements) do
[{
requirement: "1.2.3",
file: "my.csproj",
groups: [],
source: nil
}]
end
let(:previous_requirements) do
[{
requirement: "1.1.1",
file: "my.csproj",
groups: [],
source: nil
}]
end

it "updates the project correctly" do
content = updated_csproj_file.content
# Sdk attribute on Project (front, middle, back)
expect(content).to include(%(Sdk="Foo.Bar/1.2.3;))
expect(content).to include(%(X;Foo.Bar/1.2.3;Y))
expect(content).to include(%(Y;Foo.Bar/1.2.3">))
# Sdk tag (name/version and version/name)
expect(content).to include(%(<Sdk Version="1.2.3" Name="Foo.Bar"))
expect(content).to include(%(<Sdk Name="Foo.Bar" Version="1.2.3"))
# Import tag (name/version and version/name)
expect(content).to include(
%(<Import Project="X" Version="1.2.3" Sdk="Foo.Bar")
)
expect(content).to include(
%(<Import Sdk="Foo.Bar" Project="Y" Version="1.2.3")
)
end
end
end

context "with a packages.config file" do
11 changes: 11 additions & 0 deletions nuget/spec/fixtures/csproj/sdk_reference_via_import.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project>

<Import Project="Awesome.props" Sdk="Awesome.Sdk" Version="1.2.3" />

<PropertyGroup>
<Description>Very simple project using a custom SDK.</Description>
</PropertyGroup>

<Import Project="Prototype.targets" Sdk="Prototype.Sdk" Version="0.1.0-beta" />

</Project>
7 changes: 7 additions & 0 deletions nuget/spec/fixtures/csproj/sdk_reference_via_project.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk;Awesome.Sdk/1.2.3;Prototype.Sdk/0.1.0-beta">

<PropertyGroup>
<Description>Very simple project using a custom SDK.</Description>
</PropertyGroup>

</Project>
11 changes: 11 additions & 0 deletions nuget/spec/fixtures/csproj/sdk_reference_via_sdk.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project>

<Sdk Name="Awesome.Sdk" Version="1.2.3" />

<PropertyGroup>
<Description>Very simple project using a custom SDK.</Description>
</PropertyGroup>

<Sdk Name="Prototype.Sdk" Version="0.1.0-beta" />

</Project>
13 changes: 13 additions & 0 deletions nuget/spec/fixtures/csproj/sdk_references_of_all_kinds.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Foo.Bar/1.1.1;X;Foo.Bar/1.1.1;Y;Foo.Bar/1.1.1">

<Sdk Version="1.1.1" Name="Foo.Bar" />
<Sdk Name="Foo.Bar" Version="1.1.1" />

<PropertyGroup>
<Description>MSBuild SDKs Galore!</Description>
</PropertyGroup>

<Import Project="X" Version="1.1.1" Sdk="Foo.Bar" />
<Import Sdk="Foo.Bar" Project="Y" Version="1.1.1" />

</Project>
9 changes: 8 additions & 1 deletion terraform/lib/dependabot/terraform/file_updater.rb
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ class FileUpdater < Dependabot::FileUpdaters::Base
include FileSelector

PRIVATE_MODULE_ERROR = /Could not download module.*code from\n.*\"(?<repo>\S+)\":/.freeze
MODULE_NOT_INSTALLED_ERROR = /Module not installed.*module\s*\"(?<mod>\S+)\"/m.freeze

def self.updated_files_regex
[/\.tf$/, /\.hcl$/]
@@ -110,7 +111,7 @@ def update_lockfile_declaration(updated_manifest_files) # rubocop:disable Metric
updated_manifest_files.each { |f| File.write(f.name, f.content) }

File.write(".terraform.lock.hcl", lockfile_dependency_removed)
SharedHelpers.run_shell_command("terraform providers lock #{provider_source}")
SharedHelpers.run_shell_command("terraform providers lock #{provider_source} -no-color")

updated_lockfile = File.read(".terraform.lock.hcl")
updated_dependency = updated_lockfile.scan(declaration_regex).first
@@ -122,6 +123,10 @@ def update_lockfile_declaration(updated_manifest_files) # rubocop:disable Metric
content.sub!(declaration_regex, updated_dependency)
end
rescue SharedHelpers::HelperSubprocessFailed => e
if @retrying_lock && e.message.match?(MODULE_NOT_INSTALLED_ERROR)
mod = e.message.match(MODULE_NOT_INSTALLED_ERROR).named_captures.fetch("mod")
raise Dependabot::DependencyFileNotResolvable, "Attempt to install module #{mod} failed"
end
raise if @retrying_lock || !e.message.include?("terraform init")

# NOTE: Modules need to be installed before terraform can update the
@@ -146,6 +151,8 @@ def run_terraform_init
if output.match?(PRIVATE_MODULE_ERROR)
raise PrivateSourceAuthenticationFailure, output.match(PRIVATE_MODULE_ERROR).named_captures.fetch("repo")
end

raise Dependabot::DependencyFileNotResolvable, "Error running `terraform init`: #{output}"
end
end

2 changes: 2 additions & 0 deletions terraform/lib/dependabot/terraform/registry_client.rb
Original file line number Diff line number Diff line change
@@ -33,6 +33,8 @@ def all_provider_versions(identifier:)
JSON.parse(response.body).
fetch("versions").
map { |release| version_class.new(release.fetch("version")) }
rescue Excon::Error
raise error("Could not fetch provider versions")
end

# Fetch all the versions of a module, and return a Version

0 comments on commit e1411a7

Please sign in to comment.