dependabot-nuget 0.237.0 → 0.238.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.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/nuget/cache_manager.rb +22 -0
  3. data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -0
  4. data/lib/dependabot/nuget/file_fetcher.rb +65 -61
  5. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +2 -1
  6. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +2 -1
  7. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +22 -4
  8. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +287 -15
  9. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +24 -52
  10. data/lib/dependabot/nuget/file_parser.rb +4 -1
  11. data/lib/dependabot/nuget/file_updater.rb +123 -117
  12. data/lib/dependabot/nuget/native_helpers.rb +94 -0
  13. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +85 -0
  14. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +228 -0
  15. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +114 -0
  16. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +86 -0
  17. data/lib/dependabot/nuget/update_checker/property_updater.rb +30 -3
  18. data/lib/dependabot/nuget/update_checker/repository_finder.rb +32 -10
  19. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +31 -0
  20. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +127 -0
  21. data/lib/dependabot/nuget/update_checker/version_finder.rb +47 -6
  22. data/lib/dependabot/nuget/update_checker.rb +42 -8
  23. data/lib/dependabot/nuget.rb +2 -0
  24. metadata +33 -7
  25. data/lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb +0 -70
  26. data/lib/dependabot/nuget/file_updater/project_file_declaration_finder.rb +0 -183
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d633bba8c781dee126f8f39e1646bf6fa77624c4154f599c379a7a2597ca2ab5
4
- data.tar.gz: 1b6363c64e9ec7d3d9b44d3c5eb62a4065bbfe57da21e48e95a2b45a7ecd7c3e
3
+ metadata.gz: 4d913e6e79cd35b4a00551789e2d9fbea3a71476b76a12f64adfc3427cf419b1
4
+ data.tar.gz: 103b3ed625f8a89133b2c53b6a49941826cf3663de6897e9baad943a78fa37c7
5
5
  SHA512:
6
- metadata.gz: fbf7eb348c2a69d39eb4058efa0c68da4bfd8cac52646fa6230ed9dcc4efbe1c5ac216f03d67249b8fc48fe542790cac2e6b360e7725f7d2f8f7edb023d2cb4d
7
- data.tar.gz: 9f96e9f0a24581bed453c8c16246d01c5f284e52e1972614be5b0dadea10c8de2351ec06d37c59872066bf07ea0e5605c0549437b53c883d1ebebf9689525ec8
6
+ metadata.gz: c820263ed7825b8f7270693513d8ebab3ee73169318ca5113c75cad689887b5b42c1731fd6496e12033ba7a3ff0d3a59752b7bc512e64ae93baf4a520d485a83
7
+ data.tar.gz: 7ce94d621ef880f809124364816e19aa8cb1fe1975967d8eb907cf40b1573ff2c3fe4c8fad91631f5f545433813252736c3c1db17228366dea8950f0ab50ac9a
@@ -0,0 +1,22 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/file_fetchers"
5
+ require "dependabot/file_fetchers/base"
6
+ require "set"
7
+
8
+ module Dependabot
9
+ module Nuget
10
+ class CacheManager
11
+ def self.caching_disabled?
12
+ ENV["DEPENDABOT_NUGET_CACHE_DISABLED"] == "true"
13
+ end
14
+
15
+ def self.cache(name)
16
+ @cache ||= {}
17
+ @cache[name] ||= {}
18
+ @cache[name]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -39,6 +39,21 @@ module Dependabot
39
39
  nodes.compact
40
40
  end
41
41
 
42
+ def project_file_paths
43
+ doc = Nokogiri::XML(project_file.content)
44
+ doc.remove_namespaces!
45
+ nodes = doc.xpath("/Project/ItemGroup/ProjectFile").map do |node|
46
+ attribute = node.attribute("Include")
47
+ next unless attribute
48
+
49
+ path = attribute.value.strip.tr("\\", "/")
50
+ path = File.join(current_dir, path) unless current_dir.nil?
51
+ Pathname.new(path).cleanpath.to_path
52
+ end
53
+
54
+ nodes.compact
55
+ end
56
+
42
57
  private
43
58
 
44
59
  attr_reader :project_file
@@ -15,22 +15,27 @@ module Dependabot
15
15
  require_relative "file_fetcher/import_paths_finder"
16
16
  require_relative "file_fetcher/sln_project_paths_finder"
17
17
 
18
+ BUILD_FILE_NAMES = /^Directory\.Build\.(props|targets)$/i # Directory.Build.props, Directory.Build.targets
19
+
18
20
  def self.required_files_in?(filenames)
19
21
  return true if filenames.any? { |f| f.match?(/^packages\.config$/i) }
20
22
  return true if filenames.any? { |f| f.end_with?(".sln") }
21
23
  return true if filenames.any? { |f| f.match?("^src$") }
24
+ return true if filenames.any? { |f| f.end_with?(".proj") }
22
25
 
23
26
  filenames.any? { |name| name.match?(%r{^[^/]*\.[a-z]{2}proj$}) }
24
27
  end
25
28
 
26
29
  def self.required_files_message
27
- "Repo must contain a .(cs|vb|fs)proj file or a packages.config."
30
+ "Repo must contain a .proj file, .(cs|vb|fs)proj file, or a packages.config."
28
31
  end
29
32
 
33
+ # rubocop:disable Metrics/AbcSize
30
34
  sig { override.returns(T::Array[DependencyFile]) }
31
35
  def fetch_files
32
36
  fetched_files = []
33
37
  fetched_files += project_files
38
+ fetched_files += project_files.filter_map { |f| directory_packages_props_file_from_project_file(f) }
34
39
  fetched_files += directory_build_files
35
40
  fetched_files += imported_property_files
36
41
 
@@ -40,7 +45,10 @@ module Dependabot
40
45
  fetched_files << dotnet_tools_json if dotnet_tools_json
41
46
  fetched_files << packages_props if packages_props
42
47
 
43
- fetched_files = fetched_files.uniq
48
+ # dedup files based on their absolute path
49
+ fetched_files = fetched_files.uniq do |fetched_file|
50
+ Pathname.new(File.join(fetched_file.directory, fetched_file.name)).cleanpath.to_path
51
+ end
44
52
 
45
53
  if project_files.none? && packages_config_files.none?
46
54
  raise @missing_sln_project_file_errors.first if @missing_sln_project_file_errors&.any?
@@ -53,6 +61,7 @@ module Dependabot
53
61
 
54
62
  fetched_files
55
63
  end
64
+ # rubocop:enable Metrics/AbcSize
56
65
 
57
66
  private
58
67
 
@@ -60,10 +69,9 @@ module Dependabot
60
69
  @project_files ||=
61
70
  begin
62
71
  project_files = []
63
- project_files << csproj_file if csproj_file
64
- project_files << vbproj_file if vbproj_file
65
- project_files << fsproj_file if fsproj_file
66
- project_files << directory_packages_props_file if directory_packages_props_file
72
+ project_files += csproj_file
73
+ project_files += vbproj_file
74
+ project_files += fsproj_file
67
75
 
68
76
  project_files += sln_project_files
69
77
  project_files
@@ -112,49 +120,37 @@ module Dependabot
112
120
  @directory_build_files ||= fetch_directory_build_files
113
121
  end
114
122
 
123
+ # rubocop:disable Metrics/AbcSize
115
124
  def fetch_directory_build_files
116
- attempted_paths = []
125
+ attempted_dirs = []
117
126
  directory_build_files = []
118
-
119
- # Don't need to insert "." here, because Directory.Build.props files
120
- # can only be used by project files (not packages.config ones)
121
- project_files.map { |f| File.dirname(f.name) }.uniq.map do |dir|
122
- possible_paths = dir.split("/").flat_map.with_index do |_, i|
123
- base = dir.split("/").first(i + 1).join("/")
124
- possible_build_file_paths(base)
127
+ directory_path = Pathname.new(directory)
128
+
129
+ # find all build files (Directory.Build.props/.targets) relative to the given project file
130
+ project_files.map { |f| File.dirname(File.join(f.directory, f.name)) }.uniq.each do |dir|
131
+ # Simulate MSBuild walking up the directory structure looking for a file
132
+ possible_dirs = dir.split("/").map.with_index do |_, i|
133
+ candidate_dir = dir.split("/").first(i + 1).join("/")
134
+ candidate_dir = "/#{candidate_dir}" unless candidate_dir.start_with?("/")
135
+ candidate_dir
125
136
  end.reverse
126
137
 
127
- possible_paths += [
128
- "Directory.Build.props",
129
- "Directory.build.props",
130
- "Directory.Packages.props",
131
- "Directory.packages.props",
132
- "Directory.Build.targets",
133
- "Directory.build.targets"
134
- ]
135
-
136
- possible_paths.each do |path|
137
- break if attempted_paths.include?(path)
138
-
139
- attempted_paths << path
140
- file = fetch_file_if_present(path)
141
- directory_build_files << file if file
138
+ possible_dirs.each do |possible_dir|
139
+ break if attempted_dirs.include?(possible_dir)
140
+
141
+ attempted_dirs << possible_dir
142
+ relative_possible_dir = Pathname.new(possible_dir).relative_path_from(directory_path).to_s
143
+ build_files = repo_contents(dir: relative_possible_dir).select { |f| f.name.match?(BUILD_FILE_NAMES) }
144
+ directory_build_files += build_files.map do |file|
145
+ possible_file = File.join(relative_possible_dir, file.name).delete_prefix("/")
146
+ fetch_file_from_host(possible_file)
147
+ end
142
148
  end
143
149
  end
144
150
 
145
151
  directory_build_files
146
152
  end
147
-
148
- def possible_build_file_paths(base)
149
- [
150
- Pathname.new(base + "/Directory.Build.props").cleanpath.to_path,
151
- Pathname.new(base + "/Directory.build.props").cleanpath.to_path,
152
- Pathname.new(base + "/Directory.Packages.props").cleanpath.to_path,
153
- Pathname.new(base + "/Directory.packages.props").cleanpath.to_path,
154
- Pathname.new(base + "/Directory.Build.targets").cleanpath.to_path,
155
- Pathname.new(base + "/Directory.build.targets").cleanpath.to_path
156
- ]
157
- end
153
+ # rubocop:enable Metrics/AbcSize
158
154
 
159
155
  def sln_project_files
160
156
  return [] unless sln_files
@@ -189,35 +185,42 @@ module Dependabot
189
185
  end
190
186
 
191
187
  def csproj_file
192
- @csproj_file ||=
193
- begin
194
- file = repo_contents.find { |f| f.name.end_with?(".csproj") }
195
- fetch_file_from_host(file.name) if file
196
- end
188
+ @csproj_file ||= find_and_fetch_with_suffix(".csproj")
197
189
  end
198
190
 
199
191
  def vbproj_file
200
- @vbproj_file ||=
201
- begin
202
- file = repo_contents.find { |f| f.name.end_with?(".vbproj") }
203
- fetch_file_from_host(file.name) if file
204
- end
192
+ @vbproj_file ||= find_and_fetch_with_suffix(".vbproj")
205
193
  end
206
194
 
207
195
  def fsproj_file
208
- @fsproj_file ||=
209
- begin
210
- file = repo_contents.find { |f| f.name.end_with?(".fsproj") }
211
- fetch_file_from_host(file.name) if file
212
- end
196
+ @fsproj_file ||= find_and_fetch_with_suffix(".fsproj")
213
197
  end
214
198
 
215
- def directory_packages_props_file
216
- @directory_packages_props_file ||=
217
- begin
218
- file = repo_contents.find { |f| f.name.casecmp?("directory.packages.props") }
219
- fetch_file_from_host(file.name) if file
199
+ def directory_packages_props_file_from_project_file(project_file)
200
+ # walk up the tree from each project file stopping at the first `Directory.Packages.props` file found
201
+ # https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management#central-package-management-rules
202
+
203
+ found_directory_packages_props_file = nil
204
+ directory_path = Pathname.new(directory)
205
+ full_project_dir = File.dirname(File.join(project_file.directory, project_file.name))
206
+ full_project_dir.split("/").each.with_index do |_, i|
207
+ break if found_directory_packages_props_file
208
+
209
+ base = full_project_dir.split("/").first(i + 1).join("/")
210
+ candidate_file_path = Pathname.new(base + "/Directory.Packages.props").cleanpath.to_path
211
+ candidate_directory = Pathname.new(File.dirname(candidate_file_path))
212
+ relative_candidate_directory = candidate_directory.relative_path_from(directory_path)
213
+ candidate_file = repo_contents(dir: relative_candidate_directory).find do |f|
214
+ f.name.casecmp?("Directory.Packages.props")
220
215
  end
216
+ found_directory_packages_props_file = fetch_file_from_host(candidate_file.name) if candidate_file
217
+ end
218
+
219
+ found_directory_packages_props_file
220
+ end
221
+
222
+ def find_and_fetch_with_suffix(suffix)
223
+ repo_contents.select { |f| f.name.end_with?(suffix) }.map { |f| fetch_file_from_host(f.name) }
221
224
  end
222
225
 
223
226
  def nuget_config_files
@@ -286,7 +289,8 @@ module Dependabot
286
289
  def fetch_imported_property_files(file:, previously_fetched_files:)
287
290
  paths =
288
291
  ImportPathsFinder.new(project_file: file).import_paths +
289
- ImportPathsFinder.new(project_file: file).project_reference_paths
292
+ ImportPathsFinder.new(project_file: file).project_reference_paths +
293
+ ImportPathsFinder.new(project_file: file).project_file_paths
290
294
 
291
295
  paths.flat_map do |path|
292
296
  next if previously_fetched_files.map(&:name).include?(path)
@@ -51,7 +51,8 @@ module Dependabot
51
51
  attr_reader :dotnet_tools_json
52
52
 
53
53
  def parsed_dotnet_tools_json
54
- @parsed_dotnet_tools_json ||= JSON.parse(dotnet_tools_json.content)
54
+ # Remove BOM if present as JSON should be UTF-8
55
+ @parsed_dotnet_tools_json ||= JSON.parse(dotnet_tools_json.content.delete_prefix("\uFEFF"))
55
56
  rescue JSON::ParserError
56
57
  raise Dependabot::DependencyFileNotParseable, dotnet_tools_json.path
57
58
  end
@@ -48,7 +48,8 @@ module Dependabot
48
48
  attr_reader :global_json
49
49
 
50
50
  def parsed_global_json
51
- @parsed_global_json ||= JSON.parse(global_json.content)
51
+ # Remove BOM if present as JSON should be UTF-8
52
+ @parsed_global_json ||= JSON.parse(global_json.content.delete_prefix("\uFEFF"))
52
53
  rescue JSON::ParserError
53
54
  raise Dependabot::DependencyFileNotParseable, global_json.path
54
55
  end
@@ -5,6 +5,7 @@ require "nokogiri"
5
5
 
6
6
  require "dependabot/dependency"
7
7
  require "dependabot/nuget/file_parser"
8
+ require "dependabot/nuget/cache_manager"
8
9
 
9
10
  # For details on packages.config files see:
10
11
  # https://docs.microsoft.com/en-us/nuget/reference/packages-config
@@ -16,11 +17,32 @@ module Dependabot
16
17
 
17
18
  DEPENDENCY_SELECTOR = "packages > package"
18
19
 
20
+ def self.dependency_set_cache
21
+ CacheManager.cache("packages_config_dependency_set")
22
+ end
23
+
19
24
  def initialize(packages_config:)
20
25
  @packages_config = packages_config
21
26
  end
22
27
 
23
28
  def dependency_set
29
+ return parse_dependencies if CacheManager.caching_disabled?
30
+
31
+ key = "#{packages_config.name.downcase}::#{packages_config.content.hash}"
32
+ cache = PackagesConfigParser.dependency_set_cache
33
+
34
+ cache[key] ||= parse_dependencies
35
+
36
+ dependency_set = Dependabot::FileParsers::Base::DependencySet.new
37
+ dependency_set += cache[key]
38
+ dependency_set
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :packages_config
44
+
45
+ def parse_dependencies
24
46
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
25
47
 
26
48
  doc = Nokogiri::XML(packages_config.content)
@@ -43,10 +65,6 @@ module Dependabot
43
65
  dependency_set
44
66
  end
45
67
 
46
- private
47
-
48
- attr_reader :packages_config
49
-
50
68
  def dependency_name(dependency_node)
51
69
  dependency_node.attribute("id")&.value&.strip ||
52
70
  dependency_node.at_xpath("./id")&.content&.strip