licensed 2.8.0 → 2.11.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 +4 -4
- data/.github/workflows/release.yml +11 -12
- data/.github/workflows/test.yml +71 -52
- data/.gitignore +2 -0
- data/CHANGELOG.md +48 -1
- data/README.md +11 -7
- data/docs/commands.md +8 -0
- data/docs/configuration.md +36 -0
- data/docs/sources/nuget.md +14 -0
- data/lib/licensed/cli.rb +7 -0
- data/lib/licensed/commands.rb +1 -0
- data/lib/licensed/commands/cache.rb +56 -19
- data/lib/licensed/commands/environment.rb +2 -2
- data/lib/licensed/commands/notices.rb +35 -0
- data/lib/licensed/commands/status.rb +5 -1
- data/lib/licensed/configuration.rb +72 -37
- data/lib/licensed/dependency.rb +1 -1
- data/lib/licensed/git.rb +3 -1
- data/lib/licensed/reporters.rb +1 -0
- data/lib/licensed/reporters/cache_reporter.rb +10 -10
- data/lib/licensed/reporters/list_reporter.rb +5 -5
- data/lib/licensed/reporters/notices_reporter.rb +68 -0
- data/lib/licensed/reporters/reporter.rb +3 -0
- data/lib/licensed/reporters/status_reporter.rb +7 -7
- data/lib/licensed/sources.rb +1 -0
- data/lib/licensed/sources/cabal.rb +2 -2
- data/lib/licensed/sources/go.rb +27 -20
- data/lib/licensed/sources/mix.rb +5 -1
- data/lib/licensed/sources/npm.rb +1 -0
- data/lib/licensed/sources/nuget.rb +212 -0
- data/lib/licensed/version.rb +1 -1
- data/licensed.gemspec +4 -3
- data/script/bootstrap +2 -1
- data/script/source-setup/nuget +17 -0
- metadata +37 -12
data/lib/licensed/dependency.rb
CHANGED
@@ -74,7 +74,7 @@ module Licensed
|
|
74
74
|
def license_contents
|
75
75
|
files = matched_files.reject { |f| f == package_file }
|
76
76
|
.group_by(&:content)
|
77
|
-
.map { |content,
|
77
|
+
.map { |content, sources| { "sources" => license_content_sources(sources), "text" => content } }
|
78
78
|
|
79
79
|
files << generated_license_contents if files.empty?
|
80
80
|
files.compact
|
data/lib/licensed/git.rb
CHANGED
@@ -11,7 +11,9 @@ module Licensed
|
|
11
11
|
# or nil if not in a git repository.
|
12
12
|
def repository_root
|
13
13
|
return unless available?
|
14
|
-
Licensed::Shell.execute("git", "rev-parse", "--show-toplevel", allow_failure: true)
|
14
|
+
root = Licensed::Shell.execute("git", "rev-parse", "--show-toplevel", allow_failure: true)
|
15
|
+
return nil if root.empty?
|
16
|
+
root
|
15
17
|
end
|
16
18
|
|
17
19
|
# Returns true if a git repository is found, false otherwise
|
data/lib/licensed/reporters.rb
CHANGED
@@ -27,32 +27,32 @@ module Licensed
|
|
27
27
|
shell.info " #{source.class.type}"
|
28
28
|
result = yield report
|
29
29
|
|
30
|
-
warning_reports = report.all_reports.select { |
|
30
|
+
warning_reports = report.all_reports.select { |r| r.warnings.any? }.to_a
|
31
31
|
if warning_reports.any?
|
32
32
|
shell.newline
|
33
33
|
shell.warn " * Warnings:"
|
34
|
-
warning_reports.each do |
|
35
|
-
display_metadata =
|
34
|
+
warning_reports.each do |r|
|
35
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
36
36
|
|
37
|
-
shell.warn " * #{
|
37
|
+
shell.warn " * #{r.name}"
|
38
38
|
shell.warn " #{display_metadata}" unless display_metadata.empty?
|
39
|
-
|
39
|
+
r.warnings.each do |warning|
|
40
40
|
shell.warn " - #{warning}"
|
41
41
|
end
|
42
42
|
shell.newline
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
errored_reports = report.all_reports.select { |
|
46
|
+
errored_reports = report.all_reports.select { |r| r.errors.any? }.to_a
|
47
47
|
if errored_reports.any?
|
48
48
|
shell.newline
|
49
49
|
shell.error " * Errors:"
|
50
|
-
errored_reports.each do |
|
51
|
-
display_metadata =
|
50
|
+
errored_reports.each do |r|
|
51
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
52
52
|
|
53
|
-
shell.error " * #{
|
53
|
+
shell.error " * #{r.name}"
|
54
54
|
shell.error " #{display_metadata}" unless display_metadata.empty?
|
55
|
-
|
55
|
+
r.errors.each do |error|
|
56
56
|
shell.error " - #{error}"
|
57
57
|
end
|
58
58
|
shell.newline
|
@@ -28,16 +28,16 @@ module Licensed
|
|
28
28
|
shell.info " #{source.class.type}"
|
29
29
|
result = yield report
|
30
30
|
|
31
|
-
errored_reports = report.all_reports.select { |
|
31
|
+
errored_reports = report.all_reports.select { |r| r.errors.any? }.to_a
|
32
32
|
if errored_reports.any?
|
33
33
|
shell.newline
|
34
34
|
shell.error " * Errors:"
|
35
|
-
errored_reports.each do |
|
36
|
-
display_metadata =
|
35
|
+
errored_reports.each do |r|
|
36
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
37
37
|
|
38
|
-
shell.error " * #{
|
38
|
+
shell.error " * #{r.name}"
|
39
39
|
shell.error " #{display_metadata}" unless display_metadata.empty?
|
40
|
-
|
40
|
+
r.errors.each do |error|
|
41
41
|
shell.error " - #{error}"
|
42
42
|
end
|
43
43
|
shell.newline
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Licensed
|
4
|
+
module Reporters
|
5
|
+
class NoticesReporter < Reporter
|
6
|
+
TEXT_SEPARATOR = "\n\n#{("-" * 5)}\n\n".freeze
|
7
|
+
LICENSE_SEPARATOR = "\n#{("*" * 5)}\n".freeze
|
8
|
+
|
9
|
+
# Reports on an application configuration in a notices command run
|
10
|
+
#
|
11
|
+
# app - An application configuration
|
12
|
+
#
|
13
|
+
# Returns the result of the yielded method
|
14
|
+
# Note - must be called from inside the `report_run` scope
|
15
|
+
def report_app(app)
|
16
|
+
super do |report|
|
17
|
+
filename = app["shared_cache"] ? "NOTICE.#{app["name"]}" : "NOTICE"
|
18
|
+
path = app.cache_path.join(filename)
|
19
|
+
shell.info "Writing notices for #{app["name"]} to #{path}"
|
20
|
+
|
21
|
+
result = yield report
|
22
|
+
|
23
|
+
File.open(path, "w") do |file|
|
24
|
+
file << "THIRD PARTY NOTICES\n"
|
25
|
+
file << LICENSE_SEPARATOR
|
26
|
+
file << report.all_reports
|
27
|
+
.map { |r| notices(r) }
|
28
|
+
.compact
|
29
|
+
.join(LICENSE_SEPARATOR)
|
30
|
+
end
|
31
|
+
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Reports on a dependency in a notices command run.
|
37
|
+
#
|
38
|
+
# dependency - An application dependency
|
39
|
+
#
|
40
|
+
# Returns the result of the yielded method
|
41
|
+
# Note - must be called from inside the `report_run` scope
|
42
|
+
def report_dependency(dependency)
|
43
|
+
super do |report|
|
44
|
+
result = yield report
|
45
|
+
shell.warn "* #{report["warning"]}" if report["warning"]
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns notices information for a dependency report
|
51
|
+
def notices(report)
|
52
|
+
return unless report.target.is_a?(Licensed::Dependency)
|
53
|
+
|
54
|
+
cached_record = report["cached_record"]
|
55
|
+
return unless cached_record
|
56
|
+
|
57
|
+
texts = cached_record.licenses.map(&:text)
|
58
|
+
texts.concat(cached_record.notices)
|
59
|
+
|
60
|
+
<<~NOTICE
|
61
|
+
#{cached_record["name"]}@#{cached_record["version"]}
|
62
|
+
|
63
|
+
#{texts.map(&:strip).reject(&:empty?).compact.join(TEXT_SEPARATOR)}
|
64
|
+
NOTICE
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -15,20 +15,20 @@ module Licensed
|
|
15
15
|
result = yield report
|
16
16
|
|
17
17
|
all_reports = report.all_reports
|
18
|
-
errored_reports = all_reports.select { |
|
18
|
+
errored_reports = all_reports.select { |r| r.errors.any? }.to_a
|
19
19
|
|
20
|
-
dependency_count = all_reports.select { |
|
21
|
-
error_count = errored_reports.sum { |
|
20
|
+
dependency_count = all_reports.select { |r| r.target.is_a?(Licensed::Dependency) }.size
|
21
|
+
error_count = errored_reports.sum { |r| r.errors.size }
|
22
22
|
|
23
23
|
if error_count > 0
|
24
24
|
shell.newline
|
25
25
|
shell.error "Errors:"
|
26
|
-
errored_reports.each do |
|
27
|
-
display_metadata =
|
26
|
+
errored_reports.each do |r|
|
27
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
28
28
|
|
29
|
-
shell.error "* #{
|
29
|
+
shell.error "* #{r.name}"
|
30
30
|
shell.error " #{display_metadata}" unless display_metadata.empty?
|
31
|
-
|
31
|
+
r.errors.each do |error|
|
32
32
|
shell.error " - #{error}"
|
33
33
|
end
|
34
34
|
shell.newline
|
data/lib/licensed/sources.rb
CHANGED
@@ -11,6 +11,7 @@ module Licensed
|
|
11
11
|
require "licensed/sources/go"
|
12
12
|
require "licensed/sources/manifest"
|
13
13
|
require "licensed/sources/npm"
|
14
|
+
require "licensed/sources/nuget"
|
14
15
|
require "licensed/sources/pip"
|
15
16
|
require "licensed/sources/pipenv"
|
16
17
|
require "licensed/sources/gradle"
|
@@ -111,11 +111,11 @@ module Licensed
|
|
111
111
|
info = package_info_command(id).strip
|
112
112
|
return missing_package(id) if info.empty?
|
113
113
|
|
114
|
-
info.lines.each_with_object({}) do |line,
|
114
|
+
info.lines.each_with_object({}) do |line, hsh|
|
115
115
|
key, value = line.split(":", 2).map(&:strip)
|
116
116
|
next unless key && value
|
117
117
|
|
118
|
-
|
118
|
+
hsh[key] = value
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
data/lib/licensed/sources/go.rb
CHANGED
@@ -15,7 +15,8 @@ module Licensed
|
|
15
15
|
def enumerate_dependencies
|
16
16
|
with_configured_gopath do
|
17
17
|
packages.map do |package|
|
18
|
-
import_path =
|
18
|
+
import_path = non_vendored_path(package["ImportPath"], root_package["ImportPath"])
|
19
|
+
import_path ||= package["ImportPath"]
|
19
20
|
error = package.dig("Error", "Err") if package["Error"]
|
20
21
|
|
21
22
|
Dependency.new(
|
@@ -86,14 +87,17 @@ module Licensed
|
|
86
87
|
# true if go standard packages includes the import path as given
|
87
88
|
return true if go_std_packages.include?(import_path)
|
88
89
|
return true if go_std_packages.include?("vendor/#{import_path}")
|
90
|
+
return true if go_std_packages.include?(import_path.sub("golang.org", "internal"))
|
89
91
|
|
90
92
|
# additional checks are only for vendored dependencies - return false
|
91
93
|
# if package isn't vendored
|
92
|
-
|
94
|
+
non_vendored_import_path = non_vendored_path(import_path, root_package["ImportPath"])
|
95
|
+
return false unless non_vendored_import_path
|
93
96
|
|
94
97
|
# return true if any of the go standard packages matches against
|
95
98
|
# the non-vendored import path
|
96
|
-
return true if go_std_packages.include?(non_vendored_import_path
|
99
|
+
return true if go_std_packages.include?(non_vendored_import_path)
|
100
|
+
return true if go_std_packages.include?(non_vendored_import_path.sub("golang.org", "internal"))
|
97
101
|
|
98
102
|
# modify the import path to look like the import path `go list` returns for vendored std packages
|
99
103
|
vendor_path = import_path.sub("#{root_package["ImportPath"]}/", "")
|
@@ -104,7 +108,7 @@ module Licensed
|
|
104
108
|
def local_package?(package)
|
105
109
|
return false unless package && package["ImportPath"]
|
106
110
|
import_path = package["ImportPath"]
|
107
|
-
import_path.start_with?(root_package["ImportPath"]) && !
|
111
|
+
import_path.start_with?(root_package["ImportPath"]) && !import_path.include?("vendor/")
|
108
112
|
end
|
109
113
|
|
110
114
|
# Returns the version for a given package
|
@@ -116,12 +120,12 @@ module Licensed
|
|
116
120
|
return go_mod["Version"] if go_mod
|
117
121
|
|
118
122
|
package_directory = package["Dir"]
|
119
|
-
return unless package_directory
|
123
|
+
return unless package_directory && File.exist?(package_directory)
|
120
124
|
|
121
125
|
# find most recent git SHA for a package, or nil if SHA is
|
122
126
|
# not available
|
123
127
|
Dir.chdir package_directory do
|
124
|
-
contents_version
|
128
|
+
contents_version(*contents_version_arguments)
|
125
129
|
end
|
126
130
|
end
|
127
131
|
|
@@ -150,34 +154,37 @@ module Licensed
|
|
150
154
|
return if package.nil?
|
151
155
|
|
152
156
|
# search root choices:
|
153
|
-
# 1. module directory if using go modules
|
157
|
+
# 1. module directory if using go modules and directory is available
|
154
158
|
# 2. vendor folder if package is vendored
|
155
159
|
# 3. package root value if available
|
156
160
|
# 4. GOPATH if the package directory is under the gopath
|
157
161
|
# 5. nil
|
158
|
-
|
159
|
-
return
|
162
|
+
module_dir = package.dig("Module", "Dir")
|
163
|
+
return module_dir if module_dir
|
164
|
+
return package["Dir"].match("^(.*/vendor)/.*$")[1] if vendored_path?(package["Dir"], config.root)
|
160
165
|
return package["Root"] if package["Root"]
|
161
166
|
return gopath if package["Dir"]&.start_with?(gopath)
|
162
167
|
nil
|
163
168
|
end
|
164
169
|
|
165
|
-
# Returns whether a package is vendored or not based on
|
166
|
-
#
|
170
|
+
# Returns whether a package is vendored or not based on a base path and
|
171
|
+
# whether the path contains a vendor component
|
167
172
|
#
|
168
173
|
# path - Package path to test
|
169
|
-
|
170
|
-
|
171
|
-
path.
|
174
|
+
# base - The base path that the input must start with
|
175
|
+
def vendored_path?(path, base)
|
176
|
+
return false if path.nil? || base.nil?
|
177
|
+
path.start_with?(base.to_s) && path.include?("vendor/")
|
172
178
|
end
|
173
179
|
|
174
|
-
# Returns the
|
180
|
+
# Returns the path parameter without the vendor component if one is found
|
175
181
|
#
|
176
|
-
#
|
177
|
-
|
178
|
-
|
179
|
-
return
|
180
|
-
|
182
|
+
# path - Package path with vendor component
|
183
|
+
# base - The base path that the input must start with
|
184
|
+
def non_vendored_path(path, base)
|
185
|
+
return unless path
|
186
|
+
return unless vendored_path?(path, base)
|
187
|
+
path.split("vendor/")[1]
|
181
188
|
end
|
182
189
|
|
183
190
|
# Returns a hash of information about the package with a given import path
|
data/lib/licensed/sources/mix.rb
CHANGED
@@ -93,8 +93,12 @@ module Licensed
|
|
93
93
|
:[a-zA-Z0-9_]+ # after an Elixir atom,
|
94
94
|
,\s* # and skipping a comma and any number of spaces,
|
95
95
|
"(?<version>.*?)" # capture the contents of a double-quoted string as the version,
|
96
|
-
|
96
|
+
.*?\],\s* # and later
|
97
97
|
"(?<repo>.*?)" # capture the contents of a double-quoted string as the repo
|
98
|
+
(?:
|
99
|
+
,\s* # a comma
|
100
|
+
"[a-f0-9]{64}" # a digest
|
101
|
+
)?
|
98
102
|
\},?\s*\Z # right before the final closing brace.
|
99
103
|
/x,
|
100
104
|
|
data/lib/licensed/sources/npm.rb
CHANGED
@@ -48,6 +48,7 @@ module Licensed
|
|
48
48
|
# package name to it's metadata
|
49
49
|
def recursive_dependencies(dependencies, result = {})
|
50
50
|
dependencies.each do |name, dependency|
|
51
|
+
next if dependency["peerMissing"]
|
51
52
|
next if yarn_lock_present && dependency["missing"]
|
52
53
|
(result[name] ||= []) << dependency
|
53
54
|
recursive_dependencies(dependency["dependencies"] || {}, result)
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "json"
|
3
|
+
require "reverse_markdown"
|
4
|
+
|
5
|
+
module Licensed
|
6
|
+
module Sources
|
7
|
+
# Only supports ProjectReference (project.assets.json) style restore used in .NET Core.
|
8
|
+
# Does not currently support packages.config style restore.
|
9
|
+
class NuGet < Source
|
10
|
+
def self.type
|
11
|
+
"nuget"
|
12
|
+
end
|
13
|
+
|
14
|
+
class NuGetDependency < Licensed::Dependency
|
15
|
+
LICENSE_FILE_REGEX = /<license\s*type\s*=\s*\"\s*file\s*\"\s*>\s*(.*)\s*<\/license>/ix.freeze
|
16
|
+
LICENSE_URL_REGEX = /<licenseUrl>\s*(.*)\s*<\/licenseUrl>/ix.freeze
|
17
|
+
PROJECT_URL_REGEX = /<projectUrl>\s*(.*)\s*<\/projectUrl>/ix.freeze
|
18
|
+
PROJECT_DESC_REGEX = /<description>\s*(.*)\s*<\/description>/ix.freeze
|
19
|
+
|
20
|
+
# Returns the metadata that represents this dependency. This metadata
|
21
|
+
# is written to YAML in the dependencys cached text file
|
22
|
+
def license_metadata
|
23
|
+
super.tap do |record_metadata|
|
24
|
+
record_metadata["homepage"] = project_url if project_url
|
25
|
+
record_metadata["summary"] = description if description
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def nuspec_path
|
30
|
+
name = @metadata["name"]
|
31
|
+
File.join(self.path, "#{name.downcase}.nuspec")
|
32
|
+
end
|
33
|
+
|
34
|
+
def nuspec_contents
|
35
|
+
return @nuspec_contents if defined?(@nuspec_contents)
|
36
|
+
@nuspec_contents = begin
|
37
|
+
return unless nuspec_path && File.exist?(nuspec_path)
|
38
|
+
File.read(nuspec_path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def project_url
|
43
|
+
return @project_url if defined?(@project_url)
|
44
|
+
@project_url = begin
|
45
|
+
return unless nuspec_contents
|
46
|
+
match = nuspec_contents.match PROJECT_URL_REGEX
|
47
|
+
match[1] if match && match[1]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def description
|
52
|
+
return @description if defined?(@description)
|
53
|
+
@description = begin
|
54
|
+
return unless nuspec_contents
|
55
|
+
match = nuspec_contents.match PROJECT_DESC_REGEX
|
56
|
+
match[1] if match && match[1]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def project_files
|
61
|
+
@nuget_project_files ||= begin
|
62
|
+
files = super().flatten.compact
|
63
|
+
|
64
|
+
# Only include the local file if it's a file licensee didn't already detect
|
65
|
+
nuspec_license_filename = File.basename(nuspec_local_license_file.filename) if nuspec_local_license_file
|
66
|
+
if nuspec_license_filename && files.none? { |file| File.basename(file.filename) == nuspec_license_filename }
|
67
|
+
files.push(nuspec_local_license_file)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Only download licenseUrl if no recognized license was found locally
|
71
|
+
if files.none? { |file| file.license && file.license.key != "other" }
|
72
|
+
files.push(nuspec_remote_license_file)
|
73
|
+
end
|
74
|
+
|
75
|
+
files.compact
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Look for a <license type="file"> element in the nuspec that points to an
|
80
|
+
# on-disk license file (which licensee may not find due to a non-standard filename)
|
81
|
+
def nuspec_local_license_file
|
82
|
+
return @nuspec_local_license_file if defined?(@nuspec_local_license_file)
|
83
|
+
return unless nuspec_contents
|
84
|
+
|
85
|
+
match = nuspec_contents.match LICENSE_FILE_REGEX
|
86
|
+
return unless match && match[1]
|
87
|
+
|
88
|
+
license_path = File.join(File.dirname(nuspec_path), match[1])
|
89
|
+
return unless File.exist?(license_path)
|
90
|
+
|
91
|
+
license_data = File.read(license_path)
|
92
|
+
@nuspec_local_license_file = Licensee::ProjectFiles::LicenseFile.new(license_data, license_path)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Look for a <licenseUrl> element in the nuspec that either is known to contain a license identifier
|
96
|
+
# in the URL, or points to license text on the internet that can be downloaded.
|
97
|
+
def nuspec_remote_license_file
|
98
|
+
return @nuspec_remote_license_file if defined?(@nuspec_remote_license_file)
|
99
|
+
return unless nuspec_contents
|
100
|
+
|
101
|
+
match = nuspec_contents.match LICENSE_URL_REGEX
|
102
|
+
return unless match && match[1]
|
103
|
+
|
104
|
+
# Attempt to fetch the license content
|
105
|
+
license_content = self.class.retrieve_license(match[1])
|
106
|
+
@nuspec_remote_license_file = Licensee::ProjectFiles::LicenseFile.new(license_content, { uri: match[1] }) if license_content
|
107
|
+
end
|
108
|
+
|
109
|
+
class << self
|
110
|
+
def strip_html(html)
|
111
|
+
return unless html
|
112
|
+
|
113
|
+
return html unless html.downcase.include?("<html")
|
114
|
+
ReverseMarkdown.convert(html, unknown_tags: :bypass)
|
115
|
+
end
|
116
|
+
|
117
|
+
def ignored_url?(url)
|
118
|
+
# Many Microsoft packages that now use <license> use this for <licenseUrl>
|
119
|
+
# No need to fetch this page - it just contains NuGet documentation
|
120
|
+
url == "https://aka.ms/deprecateLicenseUrl"
|
121
|
+
end
|
122
|
+
|
123
|
+
def text_content_url(url)
|
124
|
+
# Convert github file URLs to raw URLs
|
125
|
+
return url unless match = url.match(/https?:\/\/(?:www\.)?github.com\/([^\/]+)\/([^\/]+)\/blob\/(.*)/i)
|
126
|
+
"https://github.com/#{match[1]}/#{match[2]}/raw/#{match[3]}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def retrieve_license(url)
|
130
|
+
return unless url
|
131
|
+
return if ignored_url?(url)
|
132
|
+
|
133
|
+
# Transform URLs that are known to return HTML but have a corresponding text-based URL
|
134
|
+
text_url = text_content_url(url)
|
135
|
+
|
136
|
+
raw_content = fetch_content(text_url)
|
137
|
+
strip_html(raw_content)
|
138
|
+
end
|
139
|
+
|
140
|
+
def fetch_content(url, redirect_limit = 5)
|
141
|
+
url = URI.parse(url) if url.instance_of? String
|
142
|
+
return @response_by_url[url] if (@response_by_url ||= {}).key?(url)
|
143
|
+
return if redirect_limit == 0
|
144
|
+
|
145
|
+
begin
|
146
|
+
response = Net::HTTP.get_response(url)
|
147
|
+
case response
|
148
|
+
when Net::HTTPSuccess then
|
149
|
+
@response_by_url[url] = response.body
|
150
|
+
when Net::HTTPRedirection then
|
151
|
+
redirect_url = URI.parse(response["location"])
|
152
|
+
if redirect_url.relative?
|
153
|
+
redirect_url = url + redirect_url
|
154
|
+
end
|
155
|
+
# The redirect might be to a URL that requires transformation, i.e. a github file
|
156
|
+
redirect_url = text_content_url(redirect_url.to_s)
|
157
|
+
@response_by_url[url] = fetch_content(redirect_url, redirect_limit - 1)
|
158
|
+
end
|
159
|
+
rescue
|
160
|
+
# Host might no longer exist or some other error, ignore
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def project_assets_file_path
|
167
|
+
File.join(config.pwd, "project.assets.json")
|
168
|
+
end
|
169
|
+
|
170
|
+
def project_assets_file
|
171
|
+
return @project_assets_file if defined?(@project_assets_file)
|
172
|
+
@project_assets_file = File.read(project_assets_file_path)
|
173
|
+
end
|
174
|
+
|
175
|
+
def enabled?
|
176
|
+
File.exist?(project_assets_file_path)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Inspect project.assets.json files for package references.
|
180
|
+
# Ideally we'd use `dotnet list package` instead, but its output isn't
|
181
|
+
# easily machine readable and doesn't contain everything we need.
|
182
|
+
def enumerate_dependencies
|
183
|
+
json = JSON.parse(project_assets_file)
|
184
|
+
nuget_packages_dir = json["project"]["restore"]["packagesPath"]
|
185
|
+
json["targets"].each_with_object({}) do |(_, target), dependencies|
|
186
|
+
target.each do |reference_key, reference|
|
187
|
+
# Ignore project references
|
188
|
+
next unless reference["type"] == "package"
|
189
|
+
package_id_parts = reference_key.partition("/")
|
190
|
+
name = package_id_parts[0]
|
191
|
+
version = package_id_parts[-1]
|
192
|
+
id = "#{name}-#{version}"
|
193
|
+
|
194
|
+
# Already know this package from another target
|
195
|
+
next if dependencies.key?(id)
|
196
|
+
|
197
|
+
path = File.join(nuget_packages_dir, json["libraries"][reference_key]["path"])
|
198
|
+
dependencies[id] = NuGetDependency.new(
|
199
|
+
name: id,
|
200
|
+
version: version,
|
201
|
+
path: path,
|
202
|
+
metadata: {
|
203
|
+
"type" => NuGet.type,
|
204
|
+
"name" => name
|
205
|
+
}
|
206
|
+
)
|
207
|
+
end
|
208
|
+
end.values
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|