dependabot-nuget 0.237.0 → 0.239.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 (27) 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 +61 -64
  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/requirement.rb +5 -1
  14. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +85 -0
  15. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +228 -0
  16. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +119 -0
  17. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +83 -0
  18. data/lib/dependabot/nuget/update_checker/property_updater.rb +30 -3
  19. data/lib/dependabot/nuget/update_checker/repository_finder.rb +36 -10
  20. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +31 -0
  21. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +127 -0
  22. data/lib/dependabot/nuget/update_checker/version_finder.rb +47 -6
  23. data/lib/dependabot/nuget/update_checker.rb +42 -8
  24. data/lib/dependabot/nuget.rb +2 -0
  25. metadata +35 -9
  26. data/lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb +0 -70
  27. 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: 2bf5bdfc4f365f5d04c30193e1b44e7a82ddb5816461148e4172cabc1a86bea3
4
+ data.tar.gz: 0ca7483d0fdc3d3a7dc2af7c8f475e7b02a121d4a8a5c58f495ff857d32c3dad
5
5
  SHA512:
6
- metadata.gz: fbf7eb348c2a69d39eb4058efa0c68da4bfd8cac52646fa6230ed9dcc4efbe1c5ac216f03d67249b8fc48fe542790cac2e6b360e7725f7d2f8f7edb023d2cb4d
7
- data.tar.gz: 9f96e9f0a24581bed453c8c16246d01c5f284e52e1972614be5b0dadea10c8de2351ec06d37c59872066bf07ea0e5605c0549437b53c883d1ebebf9689525ec8
6
+ metadata.gz: a0bd5448386d99ac0fa6a27ea6f5ec137885cd8a208762602d69284c1cdea6bb9e521a885b9a50cb42eb45b2de9ec760def87940c15023a910f7f9455a612fc9
7
+ data.tar.gz: 4096cfc58f861ec2f4f5d7a4022fd06f001d0f4421130bf0708e877a93ce2e12e722259d60b29f8303375d2e3cdc5f595e396cb4273d5c1e03a34e23a3a154ec
@@ -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,16 +15,19 @@ 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
 
30
33
  sig { override.returns(T::Array[DependencyFile]) }
@@ -40,7 +43,10 @@ module Dependabot
40
43
  fetched_files << dotnet_tools_json if dotnet_tools_json
41
44
  fetched_files << packages_props if packages_props
42
45
 
43
- fetched_files = fetched_files.uniq
46
+ # dedup files based on their absolute path
47
+ fetched_files = fetched_files.uniq do |fetched_file|
48
+ Pathname.new(fetched_file.directory).join(fetched_file.name).cleanpath.to_path
49
+ end
44
50
 
45
51
  if project_files.none? && packages_config_files.none?
46
52
  raise @missing_sln_project_file_errors.first if @missing_sln_project_file_errors&.any?
@@ -60,12 +66,12 @@ module Dependabot
60
66
  @project_files ||=
61
67
  begin
62
68
  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
67
-
69
+ project_files += csproj_file
70
+ project_files += vbproj_file
71
+ project_files += fsproj_file
68
72
  project_files += sln_project_files
73
+ project_files += proj_files
74
+ project_files += project_files.filter_map { |f| directory_packages_props_file_from_project_file(f) }
69
75
  project_files
70
76
  end
71
77
  rescue Octokit::NotFound, Gitlab::Error::NotFound
@@ -113,49 +119,29 @@ module Dependabot
113
119
  end
114
120
 
115
121
  def fetch_directory_build_files
116
- attempted_paths = []
122
+ attempted_dirs = []
117
123
  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)
125
- end.reverse
126
-
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
124
+ directory_path = Pathname.new(directory)
125
+
126
+ # find all build files (Directory.Build.props/.targets) relative to the given project file
127
+ project_files.map { |f| Pathname.new(f.directory).join(f.name).dirname }.uniq.each do |dir|
128
+ # Simulate MSBuild walking up the directory structure looking for a file
129
+ dir.descend.each do |possible_dir|
130
+ break if attempted_dirs.include?(possible_dir)
131
+
132
+ attempted_dirs << possible_dir
133
+ relative_possible_dir = Pathname.new(possible_dir).relative_path_from(directory_path).to_s
134
+ build_files = repo_contents(dir: relative_possible_dir).select { |f| f.name.match?(BUILD_FILE_NAMES) }
135
+ directory_build_files += build_files.map do |file|
136
+ possible_file = File.join(relative_possible_dir, file.name).delete_prefix("/")
137
+ fetch_file_from_host(possible_file)
138
+ end
142
139
  end
143
140
  end
144
141
 
145
142
  directory_build_files
146
143
  end
147
144
 
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
158
-
159
145
  def sln_project_files
160
146
  return [] unless sln_files
161
147
 
@@ -189,35 +175,45 @@ module Dependabot
189
175
  end
190
176
 
191
177
  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
178
+ @csproj_file ||= find_and_fetch_with_suffix(".csproj")
197
179
  end
198
180
 
199
181
  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
182
+ @vbproj_file ||= find_and_fetch_with_suffix(".vbproj")
205
183
  end
206
184
 
207
185
  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
186
+ @fsproj_file ||= find_and_fetch_with_suffix(".fsproj")
213
187
  end
214
188
 
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
189
+ def proj_files
190
+ @proj_files ||= find_and_fetch_with_suffix(".proj")
191
+ end
192
+
193
+ def directory_packages_props_file_from_project_file(project_file)
194
+ # walk up the tree from each project file stopping at the first `Directory.Packages.props` file found
195
+ # https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management#central-package-management-rules
196
+
197
+ found_directory_packages_props_file = nil
198
+ directory_path = Pathname.new(directory)
199
+ full_project_dir = Pathname.new(project_file.directory).join(project_file.name).dirname
200
+ full_project_dir.ascend.each do |base|
201
+ break if found_directory_packages_props_file
202
+
203
+ candidate_file_path = Pathname.new(base).join("Directory.Packages.props").cleanpath.to_path
204
+ candidate_directory = Pathname.new(File.dirname(candidate_file_path))
205
+ relative_candidate_directory = candidate_directory.relative_path_from(directory_path)
206
+ candidate_file = repo_contents(dir: relative_candidate_directory).find do |f|
207
+ f.name.casecmp?("Directory.Packages.props")
220
208
  end
209
+ found_directory_packages_props_file = fetch_file_from_host(candidate_file.name) if candidate_file
210
+ end
211
+
212
+ found_directory_packages_props_file
213
+ end
214
+
215
+ def find_and_fetch_with_suffix(suffix)
216
+ repo_contents.select { |f| f.name.end_with?(suffix) }.map { |f| fetch_file_from_host(f.name) }
221
217
  end
222
218
 
223
219
  def nuget_config_files
@@ -286,7 +282,8 @@ module Dependabot
286
282
  def fetch_imported_property_files(file:, previously_fetched_files:)
287
283
  paths =
288
284
  ImportPathsFinder.new(project_file: file).import_paths +
289
- ImportPathsFinder.new(project_file: file).project_reference_paths
285
+ ImportPathsFinder.new(project_file: file).project_reference_paths +
286
+ ImportPathsFinder.new(project_file: file).project_file_paths
290
287
 
291
288
  paths.flat_map do |path|
292
289
  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