licensed 3.0.0 → 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +19 -0
- data/.github/workflows/release.yml +4 -4
- data/.github/workflows/test.yml +180 -47
- data/.ruby-version +1 -1
- data/CHANGELOG.md +60 -1
- data/README.md +25 -79
- data/docker/Dockerfile.build-linux +1 -1
- data/docs/adding_a_new_source.md +11 -8
- data/docs/commands/README.md +59 -0
- data/docs/commands/cache.md +35 -0
- data/docs/commands/env.md +10 -0
- data/docs/commands/list.md +23 -0
- data/docs/commands/migrate.md +10 -0
- data/docs/commands/notices.md +12 -0
- data/docs/commands/status.md +74 -0
- data/docs/commands/version.md +3 -0
- data/docs/configuration/README.md +11 -0
- data/docs/configuration/allowed_licenses.md +17 -0
- data/docs/configuration/application_name.md +63 -0
- data/docs/configuration/application_source.md +64 -0
- data/docs/configuration/configuration_root.md +27 -0
- data/docs/configuration/configuring_multiple_apps.md +58 -0
- data/docs/configuration/dependency_source_enumerators.md +28 -0
- data/docs/configuration/ignoring_dependencies.md +19 -0
- data/docs/configuration/metadata_cache.md +106 -0
- data/docs/configuration/reviewing_dependencies.md +18 -0
- data/docs/configuration.md +9 -161
- data/docs/sources/swift.md +4 -0
- data/lib/licensed/cli.rb +2 -2
- data/lib/licensed/commands/cache.rb +19 -20
- data/lib/licensed/commands/command.rb +104 -72
- data/lib/licensed/commands/environment.rb +12 -11
- data/lib/licensed/commands/list.rb +0 -19
- data/lib/licensed/commands/notices.rb +0 -19
- data/lib/licensed/commands/status.rb +13 -15
- data/lib/licensed/configuration.rb +105 -12
- data/lib/licensed/report.rb +44 -0
- data/lib/licensed/reporters/cache_reporter.rb +48 -64
- data/lib/licensed/reporters/json_reporter.rb +19 -21
- data/lib/licensed/reporters/list_reporter.rb +45 -58
- data/lib/licensed/reporters/notices_reporter.rb +33 -46
- data/lib/licensed/reporters/reporter.rb +37 -104
- data/lib/licensed/reporters/status_reporter.rb +58 -56
- data/lib/licensed/reporters/yaml_reporter.rb +19 -21
- data/lib/licensed/sources/bundler/definition.rb +36 -0
- data/lib/licensed/sources/bundler/missing_specification.rb +1 -1
- data/lib/licensed/sources/bundler.rb +38 -86
- data/lib/licensed/sources/dep.rb +2 -2
- data/lib/licensed/sources/go.rb +3 -3
- data/lib/licensed/sources/gradle.rb +2 -2
- data/lib/licensed/sources/helpers/content_versioning.rb +2 -1
- data/lib/licensed/sources/npm.rb +4 -3
- data/lib/licensed/sources/nuget.rb +56 -27
- data/lib/licensed/sources/swift.rb +69 -0
- data/lib/licensed/sources.rb +1 -0
- data/lib/licensed/version.rb +1 -1
- data/lib/licensed.rb +1 -0
- data/licensed.gemspec +4 -4
- data/script/source-setup/go +1 -1
- data/script/source-setup/swift +22 -0
- metadata +48 -13
- data/docs/commands.md +0 -95
@@ -3,80 +3,82 @@
|
|
3
3
|
module Licensed
|
4
4
|
module Reporters
|
5
5
|
class StatusReporter < Reporter
|
6
|
-
#
|
7
|
-
# Shows the errors found when checking status, as well as
|
8
|
-
# overall number of dependencies checked
|
6
|
+
# Reports any errors encountered at the command level
|
9
7
|
#
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
# command - The command being run
|
9
|
+
# report - A report object containing information about the command run
|
10
|
+
def end_report_command(command, report)
|
11
|
+
if report.errors.any?
|
12
|
+
shell.newline
|
13
|
+
report.errors.each { |e| shell.error e }
|
14
|
+
end
|
15
|
+
end
|
14
16
|
|
15
|
-
|
17
|
+
# Reports the start of checking records for an app
|
18
|
+
#
|
19
|
+
# app - An application configuration
|
20
|
+
# report - A report containing information about the app evaluation
|
21
|
+
def begin_report_app(app, report)
|
22
|
+
shell.info "Checking cached dependency records for #{app["name"]}"
|
23
|
+
end
|
16
24
|
|
17
|
-
|
25
|
+
# Reports any errors found when checking status, as well as
|
26
|
+
# overall number of dependencies checked
|
27
|
+
#
|
28
|
+
# app - An application configuration
|
29
|
+
# report - A report containing information about the app evaluation
|
30
|
+
def end_report_app(app, report)
|
31
|
+
all_reports = report.all_reports
|
18
32
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
33
|
+
warning_reports = all_reports.select { |r| r.warnings.any? }.to_a
|
34
|
+
if warning_reports.any?
|
35
|
+
shell.newline
|
36
|
+
shell.warn "Warnings:"
|
37
|
+
warning_reports.each do |r|
|
38
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
25
39
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
shell.newline
|
40
|
+
shell.warn "* #{r.name}"
|
41
|
+
shell.warn " #{display_metadata}" unless display_metadata.empty?
|
42
|
+
r.warnings.each do |warning|
|
43
|
+
shell.warn " - #{warning}"
|
32
44
|
end
|
45
|
+
shell.newline
|
33
46
|
end
|
47
|
+
end
|
34
48
|
|
35
|
-
|
49
|
+
errored_reports = all_reports.select { |r| r.errors.any? }.to_a
|
36
50
|
|
37
|
-
|
38
|
-
|
51
|
+
dependency_count = all_reports.count { |r| r.target.is_a?(Licensed::Dependency) }
|
52
|
+
error_count = errored_reports.sum { |r| r.errors.size }
|
39
53
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
54
|
+
if error_count > 0
|
55
|
+
shell.newline
|
56
|
+
shell.error "Errors:"
|
57
|
+
errored_reports.each do |r|
|
58
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
45
59
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
shell.newline
|
60
|
+
shell.error "* #{r.name}"
|
61
|
+
shell.error " #{display_metadata}" unless display_metadata.empty?
|
62
|
+
r.errors.each do |error|
|
63
|
+
shell.error " - #{error}"
|
52
64
|
end
|
65
|
+
shell.newline
|
53
66
|
end
|
54
|
-
|
55
|
-
shell.newline
|
56
|
-
shell.info "#{dependency_count} dependencies checked, #{error_count} errors found."
|
57
|
-
|
58
|
-
result
|
59
67
|
end
|
68
|
+
|
69
|
+
shell.newline
|
70
|
+
shell.info "#{dependency_count} dependencies checked, #{error_count} errors found."
|
60
71
|
end
|
61
72
|
|
62
|
-
# Reports
|
63
|
-
# Shows whether the dependency's status is valid in dot format
|
73
|
+
# Reports whether the dependency's status is valid in dot format
|
64
74
|
#
|
65
75
|
# dependency - An application dependency
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
if report.errors.empty?
|
74
|
-
shell.confirm(".", false)
|
75
|
-
else
|
76
|
-
shell.error("F", false)
|
77
|
-
end
|
78
|
-
|
79
|
-
result
|
76
|
+
# report - A report containing information about the dependency evaluation
|
77
|
+
def end_report_dependency(dependency, report)
|
78
|
+
if report.errors.empty?
|
79
|
+
shell.confirm(".", false)
|
80
|
+
else
|
81
|
+
shell.error("F", false)
|
80
82
|
end
|
81
83
|
end
|
82
84
|
end
|
@@ -2,31 +2,29 @@
|
|
2
2
|
module Licensed
|
3
3
|
module Reporters
|
4
4
|
class YamlReporter < Reporter
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
result
|
13
|
-
end
|
5
|
+
# Report all information from the command run to the shell as a YAML object
|
6
|
+
#
|
7
|
+
# command - The command being run
|
8
|
+
# report - A report object containing information about the command run
|
9
|
+
def end_report_command(command, report)
|
10
|
+
report["apps"] = report.reports.map(&:to_h) if report.reports.any?
|
11
|
+
shell.info sanitize(report.to_h).to_yaml
|
14
12
|
end
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
# Add source report information to the app report hash
|
15
|
+
#
|
16
|
+
# app - An application configuration
|
17
|
+
# report - A report object containing information about the app evaluation
|
18
|
+
def end_report_app(app, report)
|
19
|
+
report["sources"] = report.reports.map(&:to_h) if report.reports.any?
|
22
20
|
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
# Add dependency report information to the source report hash
|
23
|
+
#
|
24
|
+
# source - A dependency source enumerator
|
25
|
+
# report - A report object containing information about the source evaluation
|
26
|
+
def end_report_source(source, report)
|
27
|
+
report["dependencies"] = report.reports.map(&:to_h) if report.reports.any?
|
30
28
|
end
|
31
29
|
|
32
30
|
def sanitize(object)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Licensed
|
4
|
+
module Bundler
|
5
|
+
module DefinitionExtensions
|
6
|
+
attr_accessor :force_exclude_groups
|
7
|
+
|
8
|
+
# Override specs to avoid logic that would raise Gem::NotFound
|
9
|
+
# which is handled in this ./missing_specification.rb, and to not add
|
10
|
+
# bundler as a dependency if it's not a user-requested gem.
|
11
|
+
#
|
12
|
+
# Newer versions of Bundler have changed the implementation of specs_for
|
13
|
+
# as well which no longer calls this function. Overriding this function
|
14
|
+
# gives a stable access point for licensed
|
15
|
+
def specs
|
16
|
+
@specs ||= begin
|
17
|
+
specs = resolve.materialize(requested_dependencies)
|
18
|
+
|
19
|
+
all_dependencies = requested_dependencies.concat(specs.flat_map(&:dependencies))
|
20
|
+
if all_dependencies.any? { |d| d.name == "bundler" } && !specs["bundler"].any?
|
21
|
+
bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", ::Bundler::VERSION)).last
|
22
|
+
specs["bundler"] = bundler
|
23
|
+
end
|
24
|
+
|
25
|
+
specs
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Override requested_groups to also exclude any groups that are
|
30
|
+
# in the "bundler.without" section of the licensed configuration file.
|
31
|
+
def requested_groups
|
32
|
+
super - Array(force_exclude_groups)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -48,7 +48,7 @@ module Bundler
|
|
48
48
|
spec = orig_materialize
|
49
49
|
return spec if spec
|
50
50
|
|
51
|
-
Licensed::Bundler::
|
51
|
+
Licensed::Bundler::MissingSpecification.new(name: name, version: version, platform: platform, source: source)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -3,6 +3,7 @@ require "delegate"
|
|
3
3
|
begin
|
4
4
|
require "bundler"
|
5
5
|
require "licensed/sources/bundler/missing_specification"
|
6
|
+
require "licensed/sources/bundler/definition"
|
6
7
|
rescue LoadError
|
7
8
|
end
|
8
9
|
|
@@ -12,9 +13,9 @@ module Licensed
|
|
12
13
|
class Dependency < Licensed::Dependency
|
13
14
|
attr_reader :loaded_from
|
14
15
|
|
15
|
-
def initialize(name:, version:, path:, loaded_from:,
|
16
|
+
def initialize(name:, version:, path:, loaded_from:, errors: [], metadata: {})
|
16
17
|
@loaded_from = loaded_from
|
17
|
-
super name: name, version: version, path: path, errors: errors, metadata: metadata
|
18
|
+
super name: name, version: version, path: path, errors: errors, metadata: metadata
|
18
19
|
end
|
19
20
|
|
20
21
|
# Load a package manager file from the base Licensee::Projects::FsProject
|
@@ -29,7 +30,7 @@ module Licensed
|
|
29
30
|
# `loaded_from` if available.
|
30
31
|
def spec_file
|
31
32
|
return @spec_file if defined?(@spec_file)
|
32
|
-
return @spec_file = nil unless loaded_from && File.
|
33
|
+
return @spec_file = nil unless loaded_from && File.file?(loaded_from)
|
33
34
|
@spec_file = begin
|
34
35
|
file = { name: File.basename(loaded_from), dir: File.dirname(loaded_from) }
|
35
36
|
Licensee::ProjectFiles::PackageManagerFile.new(File.read(loaded_from), file)
|
@@ -37,7 +38,6 @@ module Licensed
|
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
|
-
GEMFILES = { "Gemfile" => "Gemfile.lock", "gems.rb" => "gems.locked" }
|
41
41
|
DEFAULT_WITHOUT_GROUPS = %i{development test}
|
42
42
|
RUBY_PACKER_ERROR = "The bundler source cannot be used from the executable built with ruby-packer. Please install licensed using `gem install` or using bundler."
|
43
43
|
|
@@ -45,24 +45,28 @@ module Licensed
|
|
45
45
|
# running a ruby-packer-built licensed exe when ruby isn't available
|
46
46
|
# could lead to errors if the host ruby doesn't exist
|
47
47
|
return false if ruby_packer? && !Licensed::Shell.tool_available?("ruby")
|
48
|
-
|
48
|
+
|
49
|
+
# if Bundler isn't loaded, this enumerator won't work!
|
50
|
+
return false unless defined?(::Bundler)
|
51
|
+
|
52
|
+
with_application_environment { ::Bundler.default_lockfile&.exist? }
|
53
|
+
rescue ::Bundler::GemfileNotFound
|
54
|
+
false
|
49
55
|
end
|
50
56
|
|
51
57
|
def enumerate_dependencies
|
52
58
|
raise Licensed::Sources::Source::Error.new(RUBY_PACKER_ERROR) if ruby_packer?
|
53
59
|
|
54
|
-
|
55
|
-
specs.map do |spec|
|
56
|
-
next if spec.name == "bundler" && !include_bundler?
|
60
|
+
with_application_environment do
|
61
|
+
definition.specs.map do |spec|
|
57
62
|
next if spec.name == config["name"]
|
58
63
|
|
59
64
|
error = spec.error if spec.respond_to?(:error)
|
60
65
|
Dependency.new(
|
61
66
|
name: spec.name,
|
62
67
|
version: spec.version.to_s,
|
63
|
-
path: spec.
|
68
|
+
path: spec.full_gem_path,
|
64
69
|
loaded_from: spec.loaded_from,
|
65
|
-
search_root: spec_root(spec),
|
66
70
|
errors: Array(error),
|
67
71
|
metadata: {
|
68
72
|
"type" => Bundler.type,
|
@@ -74,53 +78,13 @@ module Licensed
|
|
74
78
|
end
|
75
79
|
end
|
76
80
|
|
77
|
-
# Returns an array of Gem::Specifications for all gem dependencies
|
78
|
-
def specs
|
79
|
-
@specs ||= definition.specs_for(groups)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Returns whether to include bundler as a listed dependency of the project
|
83
|
-
def include_bundler?
|
84
|
-
@include_bundler ||= begin
|
85
|
-
# include if bundler is listed as a direct dependency that should be included
|
86
|
-
requested_dependencies = definition.dependencies.select { |d| (d.groups & groups).any? && d.should_include? }
|
87
|
-
return true if requested_dependencies.any? { |d| d.name == "bundler" }
|
88
|
-
# include if bundler is an indirect dependency
|
89
|
-
return true if specs.flat_map(&:dependencies).any? { |d| d.name == "bundler" }
|
90
|
-
false
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Returns a search root for a specification, one of:
|
95
|
-
# - the local bundler gem location
|
96
|
-
# - the system rubygems install gem location
|
97
|
-
# - nil
|
98
|
-
def spec_root(spec)
|
99
|
-
return if spec.gem_dir.nil?
|
100
|
-
root = [Gem.default_dir, Gem.dir].find { |dir| spec.gem_dir.start_with?(dir) }
|
101
|
-
return unless root
|
102
|
-
|
103
|
-
"#{root}/gems/#{spec.full_name}"
|
104
|
-
end
|
105
|
-
|
106
|
-
# Build the bundler definition
|
107
81
|
def definition
|
108
|
-
@definition ||=
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
@groups ||= definition.groups - bundler_setting_array(:without) + bundler_setting_array(:with) - exclude_groups
|
115
|
-
end
|
116
|
-
|
117
|
-
# Returns a bundler setting as an array.
|
118
|
-
# Depending on the version of bundler, array values are either returned as
|
119
|
-
# a raw string ("a:b:c") or as an array ([:a, :b, :c])
|
120
|
-
def bundler_setting_array(key)
|
121
|
-
setting = ::Bundler.settings[key]
|
122
|
-
setting = setting.split(":").map(&:to_sym) if setting.is_a?(String)
|
123
|
-
Array(setting)
|
82
|
+
@definition ||= begin
|
83
|
+
definition = ::Bundler::Definition.build(::Bundler.default_gemfile, ::Bundler.default_lockfile, nil)
|
84
|
+
definition.extend Licensed::Bundler::DefinitionExtensions
|
85
|
+
definition.force_exclude_groups = exclude_groups
|
86
|
+
definition
|
87
|
+
end
|
124
88
|
end
|
125
89
|
|
126
90
|
# Returns any groups to exclude specified from both licensed configuration
|
@@ -134,41 +98,29 @@ module Licensed
|
|
134
98
|
end
|
135
99
|
end
|
136
100
|
|
137
|
-
# Returns the path to the Bundler Gemfile
|
138
|
-
def gemfile_path
|
139
|
-
@gemfile_path ||= GEMFILES.keys
|
140
|
-
.map { |g| config.pwd.join g }
|
141
|
-
.find { |f| f.exist? }
|
142
|
-
end
|
143
|
-
|
144
|
-
# Returns the path to the Bundler Gemfile.lock
|
145
|
-
def lockfile_path
|
146
|
-
return unless gemfile_path
|
147
|
-
@lockfile_path ||= gemfile_path.dirname.join(GEMFILES[gemfile_path.basename.to_s])
|
148
|
-
end
|
149
|
-
|
150
|
-
private
|
151
|
-
|
152
101
|
# helper to clear all bundler environment around a yielded block
|
153
|
-
def
|
154
|
-
|
155
|
-
original_bundle_gemfile, ENV["BUNDLE_GEMFILE"] = ENV["BUNDLE_GEMFILE"], gemfile_path.to_s
|
102
|
+
def with_application_environment
|
103
|
+
backup = nil
|
156
104
|
|
157
|
-
|
158
|
-
|
105
|
+
::Bundler.ui.silence do
|
106
|
+
if ::Bundler.root != config.source_path
|
107
|
+
backup = ENV.to_hash
|
108
|
+
ENV.replace(::Bundler.original_env)
|
159
109
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
110
|
+
# reset bundler to load from the current app's source path
|
111
|
+
::Bundler.reset!
|
112
|
+
::Bundler.load
|
113
|
+
end
|
164
114
|
|
165
|
-
|
115
|
+
yield
|
116
|
+
end
|
166
117
|
ensure
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
118
|
+
if backup
|
119
|
+
# restore bundler configuration
|
120
|
+
ENV.replace(backup)
|
121
|
+
::Bundler.reset!
|
122
|
+
::Bundler.load
|
123
|
+
end
|
172
124
|
end
|
173
125
|
|
174
126
|
# Returns whether the current licensed execution is running ruby-packer
|
data/lib/licensed/sources/dep.rb
CHANGED
@@ -40,10 +40,10 @@ module Licensed
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
# Returns the
|
43
|
+
# Returns the pkg.go.dev page for a package.
|
44
44
|
def homepage(import_path)
|
45
45
|
return unless import_path
|
46
|
-
"https://
|
46
|
+
"https://pkg.go.dev/#{import_path}"
|
47
47
|
end
|
48
48
|
|
49
49
|
# Returns whether the package is part of the go std list. Replaces
|
data/lib/licensed/sources/go.rb
CHANGED
@@ -98,7 +98,7 @@ module Licensed
|
|
98
98
|
# Returns whether the package is local to the current project
|
99
99
|
def local_package?(package)
|
100
100
|
return false unless package && package["Dir"]
|
101
|
-
return false unless File.fnmatch?("#{config.root
|
101
|
+
return false unless File.fnmatch?("#{config.root}*", package["Dir"], File::FNM_CASEFOLD)
|
102
102
|
vendored_path_parts(package).nil?
|
103
103
|
end
|
104
104
|
|
@@ -132,10 +132,10 @@ module Licensed
|
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
135
|
-
# Returns the
|
135
|
+
# Returns the pkg.go.dev page for a package.
|
136
136
|
def homepage(import_path)
|
137
137
|
return unless import_path
|
138
|
-
"https://
|
138
|
+
"https://pkg.go.dev/#{import_path}"
|
139
139
|
end
|
140
140
|
|
141
141
|
# Returns the root directory to search for a package license
|