dependabot-nuget 0.237.0 → 0.238.0

Sign up to get free protection for your applications and to get access to all the features.
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