dependabot-dep 0.90.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.
- checksums.yaml +7 -0
- data/helpers/Makefile +9 -0
- data/helpers/build +26 -0
- data/helpers/go.mod +8 -0
- data/helpers/go.sum +2 -0
- data/helpers/importresolver/go.mod +1 -0
- data/helpers/importresolver/main.go +34 -0
- data/helpers/main.go +67 -0
- data/lib/dependabot/dep.rb +11 -0
- data/lib/dependabot/dep/file_fetcher.rb +70 -0
- data/lib/dependabot/dep/file_parser.rb +189 -0
- data/lib/dependabot/dep/file_updater.rb +78 -0
- data/lib/dependabot/dep/file_updater/lockfile_updater.rb +220 -0
- data/lib/dependabot/dep/file_updater/manifest_updater.rb +151 -0
- data/lib/dependabot/dep/metadata_finder.rb +57 -0
- data/lib/dependabot/dep/native_helpers.rb +20 -0
- data/lib/dependabot/dep/path_converter.rb +72 -0
- data/lib/dependabot/dep/requirement.rb +152 -0
- data/lib/dependabot/dep/update_checker.rb +312 -0
- data/lib/dependabot/dep/update_checker/file_preparer.rb +219 -0
- data/lib/dependabot/dep/update_checker/latest_version_finder.rb +167 -0
- data/lib/dependabot/dep/update_checker/requirements_updater.rb +221 -0
- data/lib/dependabot/dep/update_checker/version_resolver.rb +166 -0
- data/lib/dependabot/dep/version.rb +43 -0
- metadata +192 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "toml-rb"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "dependabot/shared_helpers"
|
|
6
|
+
require "dependabot/dependency_file"
|
|
7
|
+
require "dependabot/dep/file_updater"
|
|
8
|
+
require "dependabot/dep/file_parser"
|
|
9
|
+
|
|
10
|
+
module Dependabot
|
|
11
|
+
module Dep
|
|
12
|
+
class FileUpdater
|
|
13
|
+
class LockfileUpdater
|
|
14
|
+
def initialize(dependencies:, dependency_files:, credentials:)
|
|
15
|
+
@dependencies = dependencies
|
|
16
|
+
@dependency_files = dependency_files
|
|
17
|
+
@credentials = credentials
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def updated_lockfile_content
|
|
21
|
+
deps = dependencies.select { |d| appears_in_lockfile(d) }
|
|
22
|
+
return lockfile.content if deps.none?
|
|
23
|
+
|
|
24
|
+
base_directory = File.join("src", "project",
|
|
25
|
+
dependency_files.first.directory)
|
|
26
|
+
base_parts = base_directory.split("/").length
|
|
27
|
+
updated_content =
|
|
28
|
+
SharedHelpers.in_a_temporary_directory(base_directory) do |dir|
|
|
29
|
+
write_temporary_dependency_files
|
|
30
|
+
|
|
31
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
|
32
|
+
# Shell out to dep, which handles everything for us.
|
|
33
|
+
# Note: We are currently doing a full install here (we're not
|
|
34
|
+
# passing no-vendor) because dep needs to generate the digests
|
|
35
|
+
# for each project.
|
|
36
|
+
command = "dep ensure -update #{deps.map(&:name).join(' ')}"
|
|
37
|
+
dir_parts = dir.realpath.to_s.split("/")
|
|
38
|
+
gopath = File.join(dir_parts[0..-(base_parts + 1)])
|
|
39
|
+
run_shell_command(command, "GOPATH" => gopath)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
File.read("Gopkg.lock")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
updated_content
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :dependencies, :dependency_files, :credentials
|
|
51
|
+
|
|
52
|
+
def run_shell_command(command, env = {})
|
|
53
|
+
start = Time.now
|
|
54
|
+
stdout, process = Open3.capture2e(env, command)
|
|
55
|
+
time_taken = start - Time.now
|
|
56
|
+
|
|
57
|
+
# Raise an error with the output from the shell session if dep
|
|
58
|
+
# returns a non-zero status
|
|
59
|
+
return if process.success?
|
|
60
|
+
|
|
61
|
+
raise SharedHelpers::HelperSubprocessFailed.new(
|
|
62
|
+
message: stdout,
|
|
63
|
+
error_context: {
|
|
64
|
+
command: command,
|
|
65
|
+
time_taken: time_taken,
|
|
66
|
+
process_exit_value: process.to_s
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def write_temporary_dependency_files
|
|
72
|
+
File.write(lockfile.name, lockfile.content)
|
|
73
|
+
|
|
74
|
+
# Overwrite the manifest with our custom prepared one
|
|
75
|
+
File.write(prepared_manifest.name, prepared_manifest.content)
|
|
76
|
+
|
|
77
|
+
File.write("hello.go", dummy_app_content)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def prepared_manifest
|
|
81
|
+
DependencyFile.new(
|
|
82
|
+
name: manifest.name,
|
|
83
|
+
content: prepared_manifest_content
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def prepared_manifest_content
|
|
88
|
+
parsed_manifest = TomlRB.parse(manifest.content)
|
|
89
|
+
|
|
90
|
+
parsed_manifest["override"] =
|
|
91
|
+
add_fsnotify_override(parsed_manifest["override"])
|
|
92
|
+
|
|
93
|
+
dependencies.each do |dep|
|
|
94
|
+
req = dep.requirements.find { |r| r[:file] == manifest.name }
|
|
95
|
+
next unless appears_in_lockfile(dep)
|
|
96
|
+
|
|
97
|
+
if req
|
|
98
|
+
update_constraint!(parsed_manifest, dep)
|
|
99
|
+
else
|
|
100
|
+
create_constraint!(parsed_manifest, dep)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
TomlRB.dump(parsed_manifest)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Used to lock the version when updating a top-level dependency
|
|
108
|
+
def update_constraint!(parsed_manifest, dep)
|
|
109
|
+
details =
|
|
110
|
+
parsed_manifest.
|
|
111
|
+
values_at(*Dep::FileParser::REQUIREMENT_TYPES).
|
|
112
|
+
flatten.compact.find { |d| d["name"] == dep.name }
|
|
113
|
+
|
|
114
|
+
req = dep.requirements.find { |r| r[:file] == manifest.name }
|
|
115
|
+
|
|
116
|
+
if req.fetch(:source).fetch(:type) == "git" && !details["branch"]
|
|
117
|
+
# Note: we don't try to update to a specific revision if the
|
|
118
|
+
# branch was previously specified because the change in
|
|
119
|
+
# specification type would be persisted in the lockfile
|
|
120
|
+
details["revision"] = dep.version if details["revision"]
|
|
121
|
+
details["version"] = dep.version if details["version"]
|
|
122
|
+
elsif req.fetch(:source).fetch(:type) == "default"
|
|
123
|
+
details.delete("branch")
|
|
124
|
+
details.delete("revision")
|
|
125
|
+
details["version"] = "=#{dep.version}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Used to lock the version when updating a subdependency
|
|
130
|
+
def create_constraint!(parsed_manifest, dep)
|
|
131
|
+
details = { "name" => dep.name }
|
|
132
|
+
|
|
133
|
+
# Fetch the details from the lockfile to check whether this
|
|
134
|
+
# sub-dependency needs a git revision or a version.
|
|
135
|
+
original_details =
|
|
136
|
+
parsed_file(lockfile).fetch("projects").
|
|
137
|
+
find { |p| p["name"] == dep.name }
|
|
138
|
+
|
|
139
|
+
if original_details["source"]
|
|
140
|
+
details["source"] = original_details["source"]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if original_details["version"]
|
|
144
|
+
details["version"] = dep.version
|
|
145
|
+
else
|
|
146
|
+
details["revision"] = dep.version
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
parsed_manifest["constraint"] ||= []
|
|
150
|
+
parsed_manifest["constraint"] << details
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Work around a dep bug that results in a panic
|
|
154
|
+
def add_fsnotify_override(overrides)
|
|
155
|
+
overrides ||= []
|
|
156
|
+
dep_name = "gopkg.in/fsnotify.v1"
|
|
157
|
+
|
|
158
|
+
override = overrides.find { |s| s["name"] == dep_name }
|
|
159
|
+
if override.nil?
|
|
160
|
+
override = { "name" => dep_name }
|
|
161
|
+
overrides << override
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
unless override["source"]
|
|
165
|
+
override["source"] = "gopkg.in/fsnotify/fsnotify.v1"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
overrides
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def dummy_app_content
|
|
172
|
+
base = "package main\n\n"\
|
|
173
|
+
"import \"fmt\"\n\n"
|
|
174
|
+
|
|
175
|
+
packages_to_import.each { |nm| base += "import \"#{nm}\"\n\n" }
|
|
176
|
+
|
|
177
|
+
base + "func main() {\n fmt.Printf(\"hello, world\\n\")\n}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def packages_to_import
|
|
181
|
+
parsed_lockfile = TomlRB.parse(lockfile.content)
|
|
182
|
+
|
|
183
|
+
# If the lockfile was created using dep v0.5.0+ then it will tell us
|
|
184
|
+
# exactly which packages to import
|
|
185
|
+
if parsed_lockfile.dig("solve-meta", "input-imports")
|
|
186
|
+
return parsed_lockfile.dig("solve-meta", "input-imports")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Otherwise we have no way of knowing, so import everything in the
|
|
190
|
+
# lockfile that isn't marked as internal
|
|
191
|
+
parsed_lockfile.fetch("projects").flat_map do |dep|
|
|
192
|
+
dep["packages"].map do |package|
|
|
193
|
+
next if package.start_with?("internal")
|
|
194
|
+
|
|
195
|
+
package == "." ? dep["name"] : File.join(dep["name"], package)
|
|
196
|
+
end.compact
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def appears_in_lockfile(dep)
|
|
201
|
+
!parsed_file(lockfile)["projects"]&.
|
|
202
|
+
find { |p| p["name"] == dep.name }.nil?
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def parsed_file(file)
|
|
206
|
+
@parsed_file ||= {}
|
|
207
|
+
@parsed_file[file.name] ||= TomlRB.parse(file.content)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def manifest
|
|
211
|
+
@manifest ||= dependency_files.find { |f| f.name == "Gopkg.toml" }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def lockfile
|
|
215
|
+
@lockfile ||= dependency_files.find { |f| f.name == "Gopkg.lock" }
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dependabot/dep/file_updater"
|
|
4
|
+
|
|
5
|
+
module Dependabot
|
|
6
|
+
module Dep
|
|
7
|
+
class FileUpdater
|
|
8
|
+
class ManifestUpdater
|
|
9
|
+
def initialize(dependencies:, manifest:)
|
|
10
|
+
@dependencies = dependencies
|
|
11
|
+
@manifest = manifest
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def updated_manifest_content
|
|
15
|
+
dependencies.
|
|
16
|
+
select { |dep| requirement_changed?(manifest, dep) }.
|
|
17
|
+
reduce(manifest.content.dup) do |content, dep|
|
|
18
|
+
updated_content = content
|
|
19
|
+
|
|
20
|
+
updated_content = update_requirements(
|
|
21
|
+
content: updated_content,
|
|
22
|
+
filename: manifest.name,
|
|
23
|
+
dependency: dep
|
|
24
|
+
)
|
|
25
|
+
updated_content = update_git_pin(
|
|
26
|
+
content: updated_content,
|
|
27
|
+
filename: manifest.name,
|
|
28
|
+
dependency: dep
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
raise "Expected content to change!" if content == updated_content
|
|
32
|
+
|
|
33
|
+
updated_content
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :dependencies, :manifest
|
|
40
|
+
|
|
41
|
+
def requirement_changed?(file, dependency)
|
|
42
|
+
changed_requirements =
|
|
43
|
+
dependency.requirements - dependency.previous_requirements
|
|
44
|
+
|
|
45
|
+
changed_requirements.any? { |f| f[:file] == file.name }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def update_requirements(content:, filename:, dependency:)
|
|
49
|
+
updated_content = content.dup
|
|
50
|
+
|
|
51
|
+
# The UpdateChecker ensures the order of requirements is preserved
|
|
52
|
+
# when updating, so we can zip them together in new/old pairs.
|
|
53
|
+
reqs = dependency.requirements.
|
|
54
|
+
zip(dependency.previous_requirements).
|
|
55
|
+
reject { |new_req, old_req| new_req == old_req }
|
|
56
|
+
|
|
57
|
+
# Loop through each changed requirement
|
|
58
|
+
reqs.each do |new_req, old_req|
|
|
59
|
+
raise "Bad req match" unless new_req[:file] == old_req[:file]
|
|
60
|
+
next if new_req[:requirement] == old_req[:requirement]
|
|
61
|
+
next unless new_req[:file] == filename
|
|
62
|
+
|
|
63
|
+
updated_content = update_manifest_req(
|
|
64
|
+
content: updated_content,
|
|
65
|
+
dep: dependency,
|
|
66
|
+
old_req: old_req.fetch(:requirement),
|
|
67
|
+
new_req: new_req.fetch(:requirement)
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
updated_content
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def update_git_pin(content:, filename:, dependency:)
|
|
75
|
+
updated_pin =
|
|
76
|
+
dependency.requirements.
|
|
77
|
+
find { |r| r[:file] == filename }&.
|
|
78
|
+
dig(:source, :ref)
|
|
79
|
+
|
|
80
|
+
old_pin =
|
|
81
|
+
dependency.previous_requirements.
|
|
82
|
+
find { |r| r[:file] == filename }&.
|
|
83
|
+
dig(:source, :ref)
|
|
84
|
+
|
|
85
|
+
return content unless old_pin
|
|
86
|
+
|
|
87
|
+
update_manifest_pin(
|
|
88
|
+
content: content,
|
|
89
|
+
dep: dependency,
|
|
90
|
+
old_pin: old_pin,
|
|
91
|
+
new_pin: updated_pin
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
96
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
97
|
+
def update_manifest_req(content:, dep:, old_req:, new_req:)
|
|
98
|
+
declaration = content.scan(declaration_regex(dep)).
|
|
99
|
+
find { |m| old_req.nil? || m.include?(old_req) }
|
|
100
|
+
|
|
101
|
+
return content unless declaration
|
|
102
|
+
|
|
103
|
+
if old_req && new_req
|
|
104
|
+
content.gsub(declaration) do |line|
|
|
105
|
+
line.gsub(old_req, new_req)
|
|
106
|
+
end
|
|
107
|
+
elsif old_req && new_req.nil?
|
|
108
|
+
content.gsub(declaration) do |line|
|
|
109
|
+
line.gsub(/\R+.*version\s*=.*/, "")
|
|
110
|
+
end
|
|
111
|
+
elsif old_req.nil? && new_req
|
|
112
|
+
content.gsub(declaration) do |line|
|
|
113
|
+
indent = line.match(/(?<indent>\s*)name/).
|
|
114
|
+
named_captures.fetch("indent")
|
|
115
|
+
version_declaration = indent + "version = \"#{new_req}\""
|
|
116
|
+
line.gsub(/name\s*=.*/) { |nm_ln| nm_ln + version_declaration }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
121
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
122
|
+
|
|
123
|
+
def update_manifest_pin(content:, dep:, old_pin:, new_pin:)
|
|
124
|
+
declaration = content.scan(declaration_regex(dep)).
|
|
125
|
+
find { |m| m.include?(old_pin) }
|
|
126
|
+
|
|
127
|
+
return content unless declaration
|
|
128
|
+
|
|
129
|
+
if old_pin && new_pin
|
|
130
|
+
content.gsub(declaration) do |line|
|
|
131
|
+
line.gsub(old_pin, new_pin)
|
|
132
|
+
end
|
|
133
|
+
elsif old_pin && new_pin.nil?
|
|
134
|
+
content.gsub(declaration) do |line|
|
|
135
|
+
line.gsub(/\R+.*(revision|branch)\s*=.*/, "")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def declaration_regex(dep)
|
|
141
|
+
/
|
|
142
|
+
(?<=\]\])
|
|
143
|
+
(?:(?!^\[).)*
|
|
144
|
+
name\s*=\s*["']#{Regexp.escape(dep.name)}["']
|
|
145
|
+
(?:(?!^\[).)*
|
|
146
|
+
/mx
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dependabot/metadata_finders"
|
|
4
|
+
require "dependabot/metadata_finders/base"
|
|
5
|
+
require "dependabot/dep/path_converter"
|
|
6
|
+
|
|
7
|
+
module Dependabot
|
|
8
|
+
module Dep
|
|
9
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def look_up_source
|
|
13
|
+
return look_up_git_dependency_source if git_dependency?
|
|
14
|
+
|
|
15
|
+
path_str = (specified_source_string || dependency.name)
|
|
16
|
+
url = Dependabot::Dep::PathConverter.
|
|
17
|
+
git_url_for_path_without_go_helper(path_str)
|
|
18
|
+
Source.from_url(url) if url
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def git_dependency?
|
|
22
|
+
return false unless declared_source_details
|
|
23
|
+
|
|
24
|
+
dependency_type =
|
|
25
|
+
declared_source_details.fetch(:type, nil) ||
|
|
26
|
+
declared_source_details.fetch("type")
|
|
27
|
+
|
|
28
|
+
dependency_type == "git"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def look_up_git_dependency_source
|
|
32
|
+
specified_url =
|
|
33
|
+
declared_source_details.fetch(:url, nil) ||
|
|
34
|
+
declared_source_details.fetch("url")
|
|
35
|
+
|
|
36
|
+
Source.from_url(specified_url)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def specified_source_string
|
|
40
|
+
declared_source_details&.fetch(:source, nil) ||
|
|
41
|
+
declared_source_details&.fetch("source", nil)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def declared_source_details
|
|
45
|
+
sources = dependency.requirements.
|
|
46
|
+
map { |r| r.fetch(:source) }.
|
|
47
|
+
uniq.compact
|
|
48
|
+
|
|
49
|
+
raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
|
|
50
|
+
|
|
51
|
+
sources.first
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
Dependabot::MetadataFinders.register("dep", Dependabot::Dep::MetadataFinder)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dependabot
|
|
4
|
+
module Dep
|
|
5
|
+
module NativeHelpers
|
|
6
|
+
def self.helper_path
|
|
7
|
+
clean_path(File.join(native_helpers_root, "dep/bin/helper"))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.native_helpers_root
|
|
11
|
+
default_path = File.join(__dir__, "../../../helpers/install-dir")
|
|
12
|
+
ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.clean_path(path)
|
|
16
|
+
Pathname.new(path).cleanpath.to_path
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|