licensed 1.5.2 → 2.0.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/.gitignore +2 -0
- data/CHANGELOG.md +22 -1
- data/CONTRIBUTING.md +2 -2
- data/README.md +17 -24
- data/Rakefile +2 -2
- data/docs/adding_a_new_source.md +93 -0
- data/docs/commands.md +81 -0
- data/docs/configuration.md +8 -8
- data/docs/migrating_to_newer_versions.md +3 -0
- data/docs/reporters.md +174 -0
- data/docs/sources/bundler.md +5 -5
- data/lib/licensed.rb +5 -14
- data/lib/licensed/cli.rb +23 -9
- data/lib/licensed/commands.rb +9 -0
- data/lib/licensed/commands/cache.rb +82 -0
- data/lib/licensed/commands/command.rb +112 -0
- data/lib/licensed/commands/list.rb +24 -0
- data/lib/licensed/commands/status.rb +49 -0
- data/lib/licensed/configuration.rb +3 -8
- data/lib/licensed/dependency.rb +116 -58
- data/lib/licensed/dependency_record.rb +76 -0
- data/lib/licensed/migrations.rb +7 -0
- data/lib/licensed/migrations/v2.rb +65 -0
- data/lib/licensed/reporters.rb +9 -0
- data/lib/licensed/reporters/cache_reporter.rb +76 -0
- data/lib/licensed/reporters/list_reporter.rb +69 -0
- data/lib/licensed/reporters/reporter.rb +119 -0
- data/lib/licensed/reporters/status_reporter.rb +67 -0
- data/lib/licensed/shell.rb +8 -10
- data/lib/licensed/sources.rb +15 -0
- data/lib/licensed/{source → sources}/bower.rb +14 -19
- data/lib/licensed/{source → sources}/bundler.rb +73 -48
- data/lib/licensed/{source → sources}/cabal.rb +40 -46
- data/lib/licensed/{source → sources}/dep.rb +15 -27
- data/lib/licensed/{source → sources}/git_submodule.rb +14 -19
- data/lib/licensed/{source → sources}/go.rb +28 -35
- data/lib/licensed/{source → sources}/manifest.rb +68 -90
- data/lib/licensed/{source → sources}/npm.rb +16 -25
- data/lib/licensed/{source → sources}/pip.rb +23 -25
- data/lib/licensed/sources/source.rb +69 -0
- data/lib/licensed/ui/shell.rb +4 -0
- data/lib/licensed/version.rb +6 -1
- data/licensed.gemspec +4 -4
- data/script/source-setup/bundler +1 -1
- metadata +32 -18
- data/lib/licensed/command/cache.rb +0 -82
- data/lib/licensed/command/list.rb +0 -43
- data/lib/licensed/command/status.rb +0 -79
- data/lib/licensed/command/version.rb +0 -18
- data/lib/licensed/license.rb +0 -68
data/docs/sources/bundler.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Bundler
|
1
|
+
# Bundler
|
2
2
|
|
3
3
|
The bundler source will detect dependencies `Gemfile` and `Gemfile.lock` files are found at an apps `source_path`. The source uses the `Bundler` API to enumerate dependencies from `Gemfile` and `Gemfile.lock`.
|
4
4
|
|
@@ -6,13 +6,13 @@ The bundler source will detect dependencies `Gemfile` and `Gemfile.lock` files a
|
|
6
6
|
|
7
7
|
**Note** this content only applies to running licensed from an executable. It does not apply when using licensed as a gem.
|
8
8
|
|
9
|
-
_It is
|
9
|
+
_It is required that the ruby runtime is available when running the licensed executable._
|
10
10
|
|
11
11
|
The licensed executable contains and runs a version of ruby. When using the Bundler APIs, a mismatch between the version of ruby built into the licensed executable and the version of licensed used during `bundle install` can occur. This mismatch can lead to licensed raising errors due to not finding dependencies.
|
12
12
|
|
13
13
|
For example, if `bundle install` was run with ruby 2.5.0 then the bundler specification path would be `<bundle path>/ruby/2.5.0/specifications`. However, if the licensed executable contains ruby 2.4.0, then licensed will be looking for specifications at `<bundle path>/ruby/2.4.0/specifications`. That path may not exist, or it may contain invalid or stale content.
|
14
14
|
|
15
|
-
|
15
|
+
To prevent confusion, licensed uses the local ruby runtime to determine the ruby version for local gems during `bundle install`. If bundler is also available, then the ruby command will be run from a `bundle exec` context.
|
16
16
|
|
17
17
|
### Excluding gem groups
|
18
18
|
|
@@ -27,14 +27,14 @@ The bundler source determines which gem groups to include or exclude with the fo
|
|
27
27
|
Strings and string arrays are both :+1:
|
28
28
|
|
29
29
|
```yml
|
30
|
-
|
30
|
+
bundler:
|
31
31
|
without: development
|
32
32
|
```
|
33
33
|
|
34
34
|
or
|
35
35
|
|
36
36
|
```yml
|
37
|
-
|
37
|
+
bundler:
|
38
38
|
without:
|
39
39
|
- build
|
40
40
|
- development
|
data/lib/licensed.rb
CHANGED
@@ -1,21 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "licensed/version"
|
3
3
|
require "licensed/shell"
|
4
|
-
require "licensed/
|
4
|
+
require "licensed/dependency_record"
|
5
5
|
require "licensed/dependency"
|
6
6
|
require "licensed/git"
|
7
|
-
require "licensed/
|
8
|
-
require "licensed/source/bower"
|
9
|
-
require "licensed/source/manifest"
|
10
|
-
require "licensed/source/npm"
|
11
|
-
require "licensed/source/go"
|
12
|
-
require "licensed/source/dep"
|
13
|
-
require "licensed/source/cabal"
|
14
|
-
require "licensed/source/pip"
|
15
|
-
require "licensed/source/git_submodule"
|
7
|
+
require "licensed/sources"
|
16
8
|
require "licensed/configuration"
|
17
|
-
require "licensed/
|
18
|
-
require "licensed/
|
19
|
-
require "licensed/command/list"
|
20
|
-
require "licensed/command/version"
|
9
|
+
require "licensed/reporters"
|
10
|
+
require "licensed/commands"
|
21
11
|
require "licensed/ui/shell"
|
12
|
+
require "licensed/migrations"
|
data/lib/licensed/cli.rb
CHANGED
@@ -8,33 +8,48 @@ module Licensed
|
|
8
8
|
desc "cache", "Cache the licenses of dependencies"
|
9
9
|
method_option :force, type: :boolean,
|
10
10
|
desc: "Overwrite licenses even if version has not changed."
|
11
|
-
method_option :offline, type: :boolean,
|
12
|
-
desc: "This option is deprecated and will be removed in the next major release."
|
13
11
|
method_option :config, aliases: "-c", type: :string,
|
14
12
|
desc: "Path to licensed configuration file"
|
15
13
|
def cache
|
16
|
-
run Licensed::
|
14
|
+
run Licensed::Commands::Cache.new(config: config), force: options[:force]
|
17
15
|
end
|
18
16
|
|
19
17
|
desc "status", "Check status of dependencies' cached licenses"
|
20
18
|
method_option :config, aliases: "-c", type: :string,
|
21
19
|
desc: "Path to licensed configuration file"
|
22
20
|
def status
|
23
|
-
run Licensed::
|
21
|
+
run Licensed::Commands::Status.new(config: config)
|
24
22
|
end
|
25
23
|
|
26
24
|
desc "list", "List dependencies"
|
27
25
|
method_option :config, aliases: "-c", type: :string,
|
28
26
|
desc: "Path to licensed configuration file"
|
29
27
|
def list
|
30
|
-
run Licensed::
|
28
|
+
run Licensed::Commands::List.new(config: config)
|
31
29
|
end
|
32
30
|
|
33
31
|
map "-v" => :version
|
34
32
|
map "--version" => :version
|
35
33
|
desc "version", "Show Installed Version of Licensed, [-v, --version]"
|
36
34
|
def version
|
37
|
-
|
35
|
+
puts Licensed::VERSION
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "migrate", "Migrate from a previous version of licensed"
|
39
|
+
method_option :config, aliases: "-c", type: :string, required: true,
|
40
|
+
desc: "Path to licensed configuration file"
|
41
|
+
method_option :from, aliases: "-f", type: :string, required: true,
|
42
|
+
desc: "Licensed version to migrate from - #{Licensed.previous_major_versions.map { |major| "v#{major}" }.join(", ")}"
|
43
|
+
def migrate
|
44
|
+
case options["from"]
|
45
|
+
when "v1"
|
46
|
+
Licensed::Migrations::V2.migrate(options["config"])
|
47
|
+
else
|
48
|
+
shell = Thor::Base.shell.new
|
49
|
+
shell.say "Unrecognized option from=#{options["from"]}", :red
|
50
|
+
CLI.command_help(shell, "migrate")
|
51
|
+
exit 1
|
52
|
+
end
|
38
53
|
end
|
39
54
|
|
40
55
|
# If an error occurs (e.g. a missing command or argument), exit 1.
|
@@ -55,9 +70,8 @@ module Licensed
|
|
55
70
|
options["config"] || Dir.pwd
|
56
71
|
end
|
57
72
|
|
58
|
-
def run(command,
|
59
|
-
command.run(
|
60
|
-
exit command.success?
|
73
|
+
def run(command, **args)
|
74
|
+
exit command.run(args)
|
61
75
|
end
|
62
76
|
end
|
63
77
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Licensed
|
3
|
+
module Commands
|
4
|
+
class Cache < Command
|
5
|
+
def initialize(config:, reporter: Licensed::Reporters::CacheReporter.new)
|
6
|
+
super(config: config, reporter: reporter)
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
# Run the command for all enumerated dependencies found in a dependency source,
|
12
|
+
# recording results in a report.
|
13
|
+
# Removes any cached records that don't match a current application
|
14
|
+
# dependency.
|
15
|
+
#
|
16
|
+
# app - The application configuration for the source
|
17
|
+
# source - A dependency source enumerator
|
18
|
+
#
|
19
|
+
# Returns whether the command succeeded for the dependency source enumerator
|
20
|
+
def run_source(app, source)
|
21
|
+
result = super
|
22
|
+
clear_stale_cached_records(app, source) if result
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
# Cache dependency record data.
|
27
|
+
#
|
28
|
+
# app - The application configuration for the dependency
|
29
|
+
# source - The dependency source enumerator for the dependency
|
30
|
+
# dependency - An application dependency
|
31
|
+
# report - A report hash for the command to provide extra data for the report output.
|
32
|
+
#
|
33
|
+
# Returns true.
|
34
|
+
def evaluate_dependency(app, source, dependency, report)
|
35
|
+
filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
|
36
|
+
cached_record = Licensed::DependencyRecord.read(filename)
|
37
|
+
if options[:force] || save_dependency_record?(dependency, cached_record)
|
38
|
+
# use the cached license value if the license text wasn't updated
|
39
|
+
dependency.record["license"] = cached_record["license"] if dependency.record.matches?(cached_record)
|
40
|
+
dependency.record.save(filename)
|
41
|
+
report["cached"] = true
|
42
|
+
end
|
43
|
+
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
# Determine if the current dependency's record should be saved.
|
48
|
+
# The record should be saved if:
|
49
|
+
# 1. there is no cached record
|
50
|
+
# 2. the cached record doesn't have a version set
|
51
|
+
# 3. the cached record version doesn't match the current dependency version
|
52
|
+
#
|
53
|
+
# dependency - An application dependency
|
54
|
+
# cached_record - A dependency record to compare with the dependency
|
55
|
+
#
|
56
|
+
# Returns true if dependency's record should be saved
|
57
|
+
def save_dependency_record?(dependency, cached_record)
|
58
|
+
return true if cached_record.nil?
|
59
|
+
|
60
|
+
cached_version = cached_record["version"]
|
61
|
+
return true if cached_version.nil? || cached_version.empty?
|
62
|
+
return true if dependency.version != cached_version
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# Clean up cached files that dont match current dependencies
|
67
|
+
#
|
68
|
+
# app - An application configuration
|
69
|
+
# source - A dependency source enumerator
|
70
|
+
#
|
71
|
+
# Returns nothing
|
72
|
+
def clear_stale_cached_records(app, source)
|
73
|
+
names = source.dependencies.map { |dependency| File.join(source.class.type, dependency.name) }
|
74
|
+
Dir.glob(app.cache_path.join(source.class.type, "**/*.#{DependencyRecord::EXTENSION}")).each do |file|
|
75
|
+
file_path = Pathname.new(file)
|
76
|
+
relative_path = file_path.relative_path_from(app.cache_path).to_s
|
77
|
+
FileUtils.rm(file) unless names.include?(relative_path.chomp(".#{DependencyRecord::EXTENSION}"))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Licensed
|
3
|
+
module Commands
|
4
|
+
class Command
|
5
|
+
attr_reader :config
|
6
|
+
attr_reader :reporter
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize(config:, reporter:)
|
10
|
+
@config = config
|
11
|
+
@reporter = reporter
|
12
|
+
end
|
13
|
+
|
14
|
+
# Run the command
|
15
|
+
#
|
16
|
+
# options - Options to run the command with
|
17
|
+
#
|
18
|
+
# Returns whether the command was a success
|
19
|
+
def run(**options)
|
20
|
+
@options = options
|
21
|
+
begin
|
22
|
+
result = reporter.report_run(self) do
|
23
|
+
config.apps.map { |app| run_app(app) }.all?
|
24
|
+
end
|
25
|
+
ensure
|
26
|
+
@options = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
# Run the command for all enabled sources for an application configuration,
|
35
|
+
# recording results in a report.
|
36
|
+
#
|
37
|
+
# app - An application configuration
|
38
|
+
#
|
39
|
+
# Returns whether the command succeeded for the application.
|
40
|
+
def run_app(app)
|
41
|
+
reporter.report_app(app) do |report|
|
42
|
+
Dir.chdir app.source_path do
|
43
|
+
begin
|
44
|
+
app.sources.select(&:enabled?).map { |source| run_source(app, source) }.all?
|
45
|
+
rescue Licensed::Shell::Error => err
|
46
|
+
report.errors << err.message
|
47
|
+
false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Run the command for all enumerated dependencies found in a dependency source,
|
54
|
+
# recording results in a report.
|
55
|
+
#
|
56
|
+
# app - The application configuration for the source
|
57
|
+
# source - A dependency source enumerator
|
58
|
+
#
|
59
|
+
# Returns whether the command succeeded for the dependency source enumerator
|
60
|
+
def run_source(app, source)
|
61
|
+
reporter.report_source(source) do |report|
|
62
|
+
begin
|
63
|
+
source.dependencies.map { |dependency| run_dependency(app, source, dependency) }.all?
|
64
|
+
rescue Licensed::Shell::Error => err
|
65
|
+
report.errors << err.message
|
66
|
+
false
|
67
|
+
rescue Licensed::Sources::Source::Error => err
|
68
|
+
report.errors << err.message
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Run the command for a dependency, evaluating the dependency and
|
75
|
+
# recording results in a report. Dependencies that were found with errors
|
76
|
+
# are not evaluated and add any errors to the dependency report.
|
77
|
+
#
|
78
|
+
# app - The application configuration for the dependency
|
79
|
+
# source - The dependency source enumerator for the dependency
|
80
|
+
# dependency - An application dependency
|
81
|
+
#
|
82
|
+
# Returns whether the command succeeded for the dependency
|
83
|
+
def run_dependency(app, source, dependency)
|
84
|
+
reporter.report_dependency(dependency) do |report|
|
85
|
+
if dependency.errors?
|
86
|
+
report.errors.concat(dependency.errors)
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
evaluate_dependency(app, source, dependency, report)
|
92
|
+
rescue Licensed::Shell::Error => err
|
93
|
+
report.errors << err.message
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Evaluate a dependency for the command. Must be implemented by a command implementation.
|
100
|
+
#
|
101
|
+
# app - The application configuration for the dependency
|
102
|
+
# source - The dependency source enumerator for the dependency
|
103
|
+
# dependency - An application dependency
|
104
|
+
# report - A report hash for the command to provide extra data for the report output.
|
105
|
+
#
|
106
|
+
# Returns whether the command succeeded for the dependency
|
107
|
+
def evaluate_dependency(app, source, dependency, report)
|
108
|
+
raise "`evaluate_dependency` must be implemented by a command"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Licensed
|
3
|
+
module Commands
|
4
|
+
class List < Command
|
5
|
+
def initialize(config:, reporter: Licensed::Reporters::ListReporter.new)
|
6
|
+
super(config: config, reporter: reporter)
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
# Listing dependencies requires no extra work.
|
12
|
+
#
|
13
|
+
# app - The application configuration for the dependency
|
14
|
+
# source - The dependency source enumerator for the dependency
|
15
|
+
# dependency - An application dependency
|
16
|
+
# report - A report hash for the command to provide extra data for the report output.
|
17
|
+
#
|
18
|
+
# Returns true.
|
19
|
+
def evaluate_dependency(app, source, dependency, report)
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
module Licensed
|
5
|
+
module Commands
|
6
|
+
class Status < Command
|
7
|
+
def initialize(config:, reporter: Licensed::Reporters::StatusReporter.new)
|
8
|
+
super(config: config, reporter: reporter)
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# Verifies that a cached record exists, is up to date and
|
14
|
+
# has license data that complies with the licensed configuration.
|
15
|
+
#
|
16
|
+
# app - The application configuration for the dependency
|
17
|
+
# source - The dependency source enumerator for the dependency
|
18
|
+
# dependency - An application dependency
|
19
|
+
# report - A report hash for the command to provide extra data for the report output.
|
20
|
+
#
|
21
|
+
# Returns whether the dependency has a cached record that is compliant
|
22
|
+
# with the licensed configuration.
|
23
|
+
def evaluate_dependency(app, source, dependency, report)
|
24
|
+
filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
|
25
|
+
report["filename"] = filename
|
26
|
+
|
27
|
+
cached_record = cached_record(filename)
|
28
|
+
if cached_record.nil?
|
29
|
+
report.errors << "cached dependency record not found"
|
30
|
+
else
|
31
|
+
report.errors << "cached dependency record out of date" if cached_record["version"] != dependency.version
|
32
|
+
report.errors << "missing license text" if cached_record.licenses.empty?
|
33
|
+
report.errors << "license needs review: #{cached_record["license"]}" unless allowed_or_reviewed?(app, cached_record)
|
34
|
+
end
|
35
|
+
|
36
|
+
report.errors.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def allowed_or_reviewed?(app, dependency)
|
40
|
+
app.allowed?(dependency) || app.reviewed?(dependency)
|
41
|
+
end
|
42
|
+
|
43
|
+
def cached_record(filename)
|
44
|
+
return nil unless File.exist?(filename)
|
45
|
+
DependencyRecord.read(filename)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -9,15 +9,10 @@ module Licensed
|
|
9
9
|
".licensed.yaml".freeze,
|
10
10
|
".licensed.json".freeze
|
11
11
|
].freeze
|
12
|
-
SOURCE_TYPES = Source.constants.map { |c| Source.const_get(c) }.freeze
|
13
|
-
|
14
|
-
attr_reader :ui
|
15
12
|
|
16
13
|
def initialize(options = {}, inherited_options = {})
|
17
14
|
super()
|
18
15
|
|
19
|
-
@ui = Licensed::UI::Shell.new
|
20
|
-
|
21
16
|
# update order:
|
22
17
|
# 1. anything inherited from root config
|
23
18
|
# 2. app defaults
|
@@ -61,9 +56,9 @@ module Licensed
|
|
61
56
|
|
62
57
|
# Returns an array of enabled app sources
|
63
58
|
def sources
|
64
|
-
@sources ||=
|
65
|
-
|
66
|
-
|
59
|
+
@sources ||= Licensed::Sources::Source.sources
|
60
|
+
.select { |source_class| enabled?(source_class.type) }
|
61
|
+
.map { |source_class| source_class.new(self) }
|
67
62
|
end
|
68
63
|
|
69
64
|
# Returns whether a source type is enabled
|
data/lib/licensed/dependency.rb
CHANGED
@@ -2,90 +2,148 @@
|
|
2
2
|
require "licensee"
|
3
3
|
|
4
4
|
module Licensed
|
5
|
-
class Dependency <
|
6
|
-
|
5
|
+
class Dependency < Licensee::Projects::FSProject
|
6
|
+
LEGAL_FILES_PATTERN = /(AUTHORS|NOTICE|LEGAL)(?:\..*)?\z/i
|
7
7
|
|
8
|
-
attr_reader :path
|
9
|
-
attr_reader :search_root
|
10
8
|
attr_reader :name
|
9
|
+
attr_reader :version
|
10
|
+
attr_reader :errors
|
11
|
+
|
12
|
+
# Create a new project dependency
|
13
|
+
#
|
14
|
+
# name - unique dependency name
|
15
|
+
# version - dependency version
|
16
|
+
# path - absolute file path to the dependency, to find license contents
|
17
|
+
# search_root - (optional) the root location to search for dependency license contents
|
18
|
+
# metadata - (optional) additional dependency data to cache
|
19
|
+
# errors - (optional) errors encountered when evaluating dependency
|
20
|
+
#
|
21
|
+
# Returns a new dependency object. Dependency metadata and license contents
|
22
|
+
# are available if no errors are set on the dependency.
|
23
|
+
def initialize(name:, version:, path:, search_root: nil, metadata: {}, errors: [])
|
24
|
+
# check the path for default errors if no other errors
|
25
|
+
# were found when loading the dependency
|
26
|
+
if errors.empty?
|
27
|
+
path_error = path_error(path, search_root)
|
28
|
+
errors.push(path_error) if path_error
|
29
|
+
end
|
11
30
|
|
12
|
-
|
13
|
-
@
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
self.path = path
|
18
|
-
end
|
31
|
+
@name = name
|
32
|
+
@version = version
|
33
|
+
@metadata = metadata
|
34
|
+
@errors = errors
|
19
35
|
|
20
|
-
|
21
|
-
|
22
|
-
@project ||= Licensee::Projects::FSProject.new(path, search_root: search_root, detect_packages: true, detect_readme: true)
|
23
|
-
end
|
36
|
+
# if there are any errors, don't evaluate any dependency contents
|
37
|
+
return if errors.any?
|
24
38
|
|
25
|
-
# Sets the path to source dependency license information
|
26
|
-
def path=(path)
|
27
39
|
# enforcing absolute paths makes life much easier when determining
|
28
40
|
# an absolute file path in #notices
|
29
|
-
|
30
|
-
|
41
|
+
if !Pathname.new(path).absolute?
|
42
|
+
# this is an internal error related to source implementation and
|
43
|
+
# should be raised, not stored to be handled by reporters
|
44
|
+
raise ArgumentError, "dependency path #{path} must be absolute"
|
31
45
|
end
|
32
46
|
|
33
|
-
|
34
|
-
reset_license!
|
47
|
+
super(path, search_root: search_root, detect_readme: true, detect_packages: true)
|
35
48
|
end
|
36
49
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
def detect_license!
|
41
|
-
self["license"] = license_key
|
42
|
-
self.text = [license_text, *notices].join("\n" + TEXT_SEPARATOR + "\n").rstrip
|
50
|
+
# Returns true if the dependency has any errors, false otherwise
|
51
|
+
def errors?
|
52
|
+
errors.any?
|
43
53
|
end
|
44
54
|
|
45
|
-
#
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
55
|
+
# Returns a record for this dependency including metadata and legal contents
|
56
|
+
def record
|
57
|
+
return nil if errors?
|
58
|
+
@record ||= DependencyRecord.new(
|
59
|
+
metadata: license_metadata,
|
60
|
+
licenses: license_contents,
|
61
|
+
notices: notice_contents
|
62
|
+
)
|
52
63
|
end
|
53
64
|
|
54
|
-
# Returns
|
55
|
-
def
|
56
|
-
return
|
65
|
+
# Returns a string representing the dependencys license
|
66
|
+
def license_key
|
67
|
+
return "none" if errors? || !license
|
68
|
+
license.key
|
69
|
+
end
|
57
70
|
|
58
|
-
|
59
|
-
|
71
|
+
# Returns the license text content from all matched sources
|
72
|
+
# except the package file, which doesn't contain license text.
|
73
|
+
def license_contents
|
74
|
+
return [] if errors?
|
75
|
+
matched_files.reject { |f| f == package_file }
|
76
|
+
.group_by(&:content)
|
77
|
+
.map { |content, files| { "sources" => content_sources(files), "text" => content } }
|
78
|
+
end
|
60
79
|
|
61
|
-
|
62
|
-
|
80
|
+
# Returns legal notices found at the dependency path
|
81
|
+
def notice_contents
|
82
|
+
return [] if errors?
|
83
|
+
notice_files.sort # sorted by the path
|
84
|
+
.map { |file| { "sources" => content_sources(file), "text" => File.read(file).rstrip } }
|
85
|
+
.select { |text| text.length > 0 } # files with content only
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns an array of file paths used to locate legal notices
|
89
|
+
def notice_files
|
90
|
+
return [] if errors?
|
63
91
|
|
64
|
-
|
65
|
-
|
92
|
+
Dir.glob(dir_path.join("*"))
|
93
|
+
.grep(LEGAL_FILES_PATTERN)
|
94
|
+
.select { |path| File.file?(path) }
|
66
95
|
end
|
67
96
|
|
68
97
|
private
|
69
98
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
99
|
+
def path_error(path, search_root)
|
100
|
+
return "dependency path not found" if path.to_s.empty?
|
101
|
+
return if File.exist?(path)
|
102
|
+
return if search_root && File.exist?(search_root)
|
103
|
+
|
104
|
+
# if the given path doesn't exist
|
105
|
+
# AND a search root isn't given, or the search root doesn't exist
|
106
|
+
# then set an error that the expected dependency path doesn't exist
|
107
|
+
"expected dependency path #{path} does not exist"
|
75
108
|
end
|
76
109
|
|
77
|
-
#
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
110
|
+
# Returns the sources for a group of license or notice file contents
|
111
|
+
#
|
112
|
+
# Sources are returned as a single string with sources separated by ", "
|
113
|
+
def content_sources(files)
|
114
|
+
paths = Array(files).map do |file|
|
115
|
+
path = if file.is_a?(Licensee::ProjectFiles::ProjectFile)
|
116
|
+
dir_path.join(file[:dir], file[:name])
|
117
|
+
else
|
118
|
+
Pathname.new(file).expand_path(dir_path)
|
119
|
+
end
|
120
|
+
|
121
|
+
if path.fnmatch?(dir_path.join("**").to_path)
|
122
|
+
# files under the dependency path return the relative path to the file
|
123
|
+
path.relative_path_from(dir_path).to_path
|
124
|
+
else
|
125
|
+
# otherwise return the source_path as the immediate parent folder name
|
126
|
+
# joined with the file name
|
127
|
+
path.dirname.basename.join(path.basename).to_path
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
paths.join(", ")
|
83
132
|
end
|
84
133
|
|
85
|
-
# Returns
|
86
|
-
|
87
|
-
|
88
|
-
|
134
|
+
# Returns the metadata that represents this dependency. This metadata
|
135
|
+
# is written to YAML in the dependencys cached text file
|
136
|
+
def license_metadata
|
137
|
+
{
|
138
|
+
# can be overriden by values in @metadata
|
139
|
+
"name" => name,
|
140
|
+
"version" => version
|
141
|
+
}.merge(
|
142
|
+
@metadata
|
143
|
+
).merge({
|
144
|
+
# overrides all other values
|
145
|
+
"license" => license_key
|
146
|
+
})
|
89
147
|
end
|
90
148
|
end
|
91
149
|
end
|