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