dependabot-nuget 0.237.0 → 0.239.0

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