licensed 4.0.4 → 4.2.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/CHANGELOG.md +18 -1
- data/Gemfile.lock +1 -1
- data/docs/configuration/README.md +1 -0
- data/docs/configuration/additional_terms.md +41 -0
- data/docs/configuration/ignoring_dependencies.md +13 -0
- data/docs/configuration/reviewing_dependencies.md +13 -0
- data/docs/configuration.md +7 -0
- data/docs/sources/pnpm.md +20 -0
- data/lib/licensed/commands/cache.rb +3 -3
- data/lib/licensed/commands/status.rb +5 -4
- data/lib/licensed/configuration.rb +48 -12
- data/lib/licensed/dependency.rb +40 -8
- data/lib/licensed/sources/gradle.rb +62 -60
- data/lib/licensed/sources/pnpm.rb +58 -0
- data/lib/licensed/sources/source.rb +15 -2
- data/lib/licensed/sources.rb +4 -3
- data/lib/licensed/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b65e198a420b03486b2680a6a83fb04b4e67684d28bc4ba9ef00c466ffd7489
|
4
|
+
data.tar.gz: ab2b10c6e854d3f1d7faa918e48addb497032838fd1cea942ff823053b891150
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8dee38c45e73cb03b7c94a9260bb9bc6f5919f53156b69a751238693920e2f120a3dcb0d43f66270fa9abc8305705fdced290978e51bfe762be5f0e5ba00230d
|
7
|
+
data.tar.gz: d352b46e40f545f0bd3e1aa29e3bb62454e2e329c64ce9197ba1f9c38b4f11bcbbbecc789658dcff0e6b79e43b4ee9cc839b5476e23cab44a559a605ddbd77b6
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## 4.2.0
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Reviewed and ignored configuration lists support matching on versions and version ranges (https://github.com/github/licensed/pull/629)
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
|
17
|
+
- Licensed should more reliably source dependencies from Gradle >= 8.0 (https://github.com/github/licensed/pull/630)
|
18
|
+
|
19
|
+
## 4.1.0
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
- Custom license terms can be added to dependencies via new configuration options (https://github.com/github/licensed/pull/624)
|
24
|
+
- Licensed is now integrated with pnpm to enumerate dependencies (https://github.com/github/licensed/pull/626)
|
25
|
+
|
9
26
|
## 4.0.4
|
10
27
|
|
11
28
|
### Changed
|
@@ -706,4 +723,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
706
723
|
|
707
724
|
Initial release :tada:
|
708
725
|
|
709
|
-
[Unreleased]: https://github.com/github/licensed/compare/4.0
|
726
|
+
[Unreleased]: https://github.com/github/licensed/compare/4.2.0...HEAD
|
data/Gemfile.lock
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Additional terms
|
2
|
+
|
3
|
+
The `additional_terms` configuration option is used to specify paths to files containing extra licensing terms that do not ship with the dependency package. All files specified are expected to be plain text.
|
4
|
+
|
5
|
+
Files containing additional content can be located anywhere on disk that is accessible to licensed. File paths can be specified as a string or array and can contain glob values to simplify configuration inputs. All file paths are evaluated from the [configuration root](./configuration_root.md).
|
6
|
+
|
7
|
+
## Examples
|
8
|
+
|
9
|
+
**Note** The examples below specify paths to additional files under the `.licenses` folder. This is a logical place to store files containing license terms, but be careful not to store files under paths managed by licensed like `.licenses/<source type>/...`. Running `licensed cache` in the future will delete any files under licensed managed paths that licensed did not create. This is why the below examples use paths like `.licenses/amendments/bundler/...` instead of not `.licenses/bundler/amendments/...`.
|
10
|
+
|
11
|
+
### With a string
|
12
|
+
|
13
|
+
```yaml
|
14
|
+
additional_terms:
|
15
|
+
# specify the type of dependency
|
16
|
+
bundler:
|
17
|
+
# specify the dependency name and path to an additional file
|
18
|
+
<gem-name>: .licenses/amendments/bundler/<gem-name>/terms.txt
|
19
|
+
```
|
20
|
+
|
21
|
+
### With a glob string
|
22
|
+
|
23
|
+
```yaml
|
24
|
+
additional_terms:
|
25
|
+
# specify the type of dependency
|
26
|
+
bundler:
|
27
|
+
# specify the dependency name and one or more additional files with a glob pattern
|
28
|
+
<gem-name>: .licenses/amendments/bundler/<gem-name>/*.txt
|
29
|
+
```
|
30
|
+
|
31
|
+
### With an array of strings
|
32
|
+
|
33
|
+
```yaml
|
34
|
+
additional_terms:
|
35
|
+
# specify the type of dependency
|
36
|
+
bundler:
|
37
|
+
# specify the dependency name and array of paths to additional files
|
38
|
+
<gem-name>:
|
39
|
+
- .licenses/amendments/bundler/<gem-name>/terms-1.txt
|
40
|
+
- .licenses/amendments/bundler/<gem-name>/terms-2.txt
|
41
|
+
```
|
@@ -17,3 +17,16 @@ ignored:
|
|
17
17
|
go:
|
18
18
|
- github.com/me/my-repo/**/*
|
19
19
|
```
|
20
|
+
|
21
|
+
## Ignoring dependencies at specific versions
|
22
|
+
|
23
|
+
Ignore a dependency at specific versions by appending `@<version>` to the end of the dependency's name in an `ignore` list. If a dependency is configured to be ignored at a specific version, licensed will not ignore non-matching versions of the dependency.
|
24
|
+
|
25
|
+
The version value can be one of:
|
26
|
+
|
27
|
+
1. `"*"` - match any version value
|
28
|
+
1. any version string, or version range string, that can be parsed by `Gem::Requirement`
|
29
|
+
- a semantic version - `dependency@1.2.3`
|
30
|
+
- a gem requirement range - `dependency@~> 1.0.0` or `dependency@< 3.0`
|
31
|
+
- see the [Rubygems version guides](https://guides.rubygems.org/patterns/#pessimistic-version-constraint) for more details about specifying gem version requirements
|
32
|
+
1. a value that can't be parsed by `Gem::Requirement`, which will only match dependencies with the same version string
|
@@ -16,3 +16,16 @@ reviewed:
|
|
16
16
|
bundler:
|
17
17
|
- gem-using-unallowed-license
|
18
18
|
```
|
19
|
+
|
20
|
+
## Reviewing dependencies at specific versions
|
21
|
+
|
22
|
+
Review a dependency at specific versions by appending `@<version>` to the end of the dependency's name in an `reviewed` list. If a dependency is configured to be reviewed at a specific version, licensed will not recognize non-matching versions of the dependency as being manually reviewed and accepted.
|
23
|
+
|
24
|
+
The version value can be one of:
|
25
|
+
|
26
|
+
1. `"*"` - match any version value
|
27
|
+
1. any version string, or version range string, that can be parsed by `Gem::Requirement`
|
28
|
+
- a semantic version - `dependency@1.2.3`
|
29
|
+
- a gem requirement range - `dependency@~> 1.0.0` or `dependency@< 3.0`
|
30
|
+
- see the [Rubygems version guides](https://guides.rubygems.org/patterns/#pessimistic-version-constraint) for more details about specifying gem version requirements
|
31
|
+
1. a value that can't be parsed by `Gem::Requirement`, which will only match dependencies with the same version string
|
data/docs/configuration.md
CHANGED
@@ -67,6 +67,13 @@ reviewed:
|
|
67
67
|
- classlist # public domain
|
68
68
|
- octicons
|
69
69
|
|
70
|
+
# Specify additional license terms that have been obtained from a dependency's owner
|
71
|
+
# which apply to the dependency's license
|
72
|
+
additional_terms:
|
73
|
+
bundler:
|
74
|
+
bcrypt-ruby:
|
75
|
+
- .licenses/amendments/bundler/bcrypt-ruby/amendment.txt
|
76
|
+
|
70
77
|
# A single configuration file can be used to enumerate dependencies for multiple
|
71
78
|
# projects. Each configuration is referred to as an "application" and must include
|
72
79
|
# a source path, at a minimum
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# pnpm
|
2
|
+
|
3
|
+
The npm source will detect dependencies when `pnpm-lock.yaml` is found at an apps `source_path`. It uses `pnpm licenses list` to enumerate dependencies and metadata.
|
4
|
+
|
5
|
+
All dependencies enumerated by the pnpm source include the dependency version in the dependency's name identifier. All [reviewed](../configuration/reviewing_dependencies.md) or [ignored](../configuration/ignoring_dependencies.md) dependencies must include a version signifier in the configured dependency name.
|
6
|
+
|
7
|
+
**NOTE** [pnpm licenses list](https://pnpm.io/cli/licenses) is an experimental CLI command and subject to change. If changes to pnpm result in unexpected or broken behavior in licensed please open an [issue](https://github.com/github/licensed/issues/new).
|
8
|
+
|
9
|
+
## Including development dependencies
|
10
|
+
|
11
|
+
By default, the npm source will exclude all development dependencies. To include development or test dependencies, set `production_only: false` in the licensed configuration.
|
12
|
+
|
13
|
+
```yml
|
14
|
+
pnpm:
|
15
|
+
production_only: false
|
16
|
+
```
|
17
|
+
|
18
|
+
## Using licensed with pnpm workspaces
|
19
|
+
|
20
|
+
Licensed will locate all dependencies from all pnpm workspaces and cannot enumerate dependencies from individual project workspaces. This is a limitation from the pnpm CLI.
|
@@ -83,7 +83,7 @@ module Licensed
|
|
83
83
|
report["version"] = dependency.version
|
84
84
|
|
85
85
|
if save_dependency_record?(dependency, cached_record)
|
86
|
-
update_dependency_from_cached_record(app, dependency, cached_record)
|
86
|
+
update_dependency_from_cached_record(app, source, dependency, cached_record)
|
87
87
|
|
88
88
|
dependency.record.save(filename)
|
89
89
|
report["cached"] = true
|
@@ -123,14 +123,14 @@ module Licensed
|
|
123
123
|
# Update dependency metadata from the cached record, to support:
|
124
124
|
# 1. continuity between cache runs to cut down on churn
|
125
125
|
# 2. notifying users when changed content needs to be reviewed
|
126
|
-
def update_dependency_from_cached_record(app, dependency, cached_record)
|
126
|
+
def update_dependency_from_cached_record(app, source, dependency, cached_record)
|
127
127
|
return if cached_record.nil?
|
128
128
|
return if options[:force]
|
129
129
|
|
130
130
|
if dependency.record.matches?(cached_record)
|
131
131
|
# use the cached license value if the license text wasn't updated
|
132
132
|
dependency.record["license"] = cached_record["license"]
|
133
|
-
elsif app.reviewed?(dependency.record)
|
133
|
+
elsif app.reviewed?(dependency.record, require_version: source.class.require_matched_dependency_version)
|
134
134
|
# if the license text changed and the dependency is set as reviewed
|
135
135
|
# force a re-review of the dependency
|
136
136
|
dependency.record["review_changed_license"] = true
|
@@ -60,7 +60,7 @@ module Licensed
|
|
60
60
|
report.errors << "missing license text" if record.licenses.empty?
|
61
61
|
if record["review_changed_license"]
|
62
62
|
report.errors << "license text has changed and needs re-review. if the new text is ok, remove the `review_changed_license` flag from the cached record"
|
63
|
-
elsif license_needs_review?(app, record)
|
63
|
+
elsif license_needs_review?(app, source, record)
|
64
64
|
report.errors << needs_review_error_message(app, record)
|
65
65
|
end
|
66
66
|
end
|
@@ -70,9 +70,10 @@ module Licensed
|
|
70
70
|
|
71
71
|
# Returns true if a cached record needs further review based on the
|
72
72
|
# record's license(s) and the app's configuration
|
73
|
-
def license_needs_review?(app, record)
|
73
|
+
def license_needs_review?(app, source, record)
|
74
74
|
# review is not needed if the record is set as reviewed
|
75
|
-
|
75
|
+
require_version = data_source == "configuration" || source.class.require_matched_dependency_version
|
76
|
+
return false if app.reviewed?(record, require_version: require_version)
|
76
77
|
|
77
78
|
# review is not needed if the top level license is allowed
|
78
79
|
return false if app.allowed?(record["license"])
|
@@ -99,7 +100,7 @@ module Licensed
|
|
99
100
|
error = "dependency needs review"
|
100
101
|
|
101
102
|
# look for an unversioned reviewed list match
|
102
|
-
if app.reviewed?(record,
|
103
|
+
if app.reviewed?(record, require_version: false)
|
103
104
|
error += ", unversioned 'reviewed' match found: #{record["name"]}"
|
104
105
|
end
|
105
106
|
|
@@ -10,6 +10,8 @@ module Licensed
|
|
10
10
|
|
11
11
|
DEFAULT_CACHE_PATH = ".licenses".freeze
|
12
12
|
|
13
|
+
ANY_VERSION_REQUIREMENT = "*".freeze
|
14
|
+
|
13
15
|
# Returns the root for a configuration in following order of precedence:
|
14
16
|
# 1. explicitly configured "root" property
|
15
17
|
# 2. a found git repository root
|
@@ -73,8 +75,8 @@ module Licensed
|
|
73
75
|
end
|
74
76
|
|
75
77
|
# Is the given dependency reviewed?
|
76
|
-
def reviewed?(dependency,
|
77
|
-
any_list_pattern_matched? self["reviewed"][dependency["type"]], dependency,
|
78
|
+
def reviewed?(dependency, require_version: false)
|
79
|
+
any_list_pattern_matched? self["reviewed"][dependency["type"]], dependency, require_version: require_version
|
78
80
|
end
|
79
81
|
|
80
82
|
# Find all reviewed dependencies that match the provided dependency's name
|
@@ -83,8 +85,8 @@ module Licensed
|
|
83
85
|
end
|
84
86
|
|
85
87
|
# Is the given dependency ignored?
|
86
|
-
def ignored?(dependency)
|
87
|
-
any_list_pattern_matched? self["ignored"][dependency["type"]], dependency
|
88
|
+
def ignored?(dependency, require_version: false)
|
89
|
+
any_list_pattern_matched? self["ignored"][dependency["type"]], dependency, require_version: require_version
|
88
90
|
end
|
89
91
|
|
90
92
|
# Is the license of the dependency allowed?
|
@@ -93,8 +95,10 @@ module Licensed
|
|
93
95
|
end
|
94
96
|
|
95
97
|
# Ignore a dependency
|
96
|
-
def ignore(dependency)
|
97
|
-
|
98
|
+
def ignore(dependency, at_version: false)
|
99
|
+
id = dependency["name"]
|
100
|
+
id += "@#{dependency["version"]}" if at_version && dependency["version"]
|
101
|
+
(self["ignored"][dependency["type"]] ||= []) << id
|
98
102
|
end
|
99
103
|
|
100
104
|
# Set a dependency as reviewed
|
@@ -109,17 +113,49 @@ module Licensed
|
|
109
113
|
self["allowed"] << license
|
110
114
|
end
|
111
115
|
|
116
|
+
# Returns an array of paths to files containing additional license terms.
|
117
|
+
def additional_terms_for_dependency(dependency)
|
118
|
+
amendment_paths = Array(self.dig("additional_terms", dependency["type"], dependency["name"]))
|
119
|
+
amendment_paths.flat_map { |path| Dir.glob(self.root.join(path)) }
|
120
|
+
end
|
121
|
+
|
112
122
|
private
|
113
123
|
|
114
|
-
def any_list_pattern_matched?(list, dependency,
|
124
|
+
def any_list_pattern_matched?(list, dependency, require_version: false)
|
115
125
|
Array(list).any? do |pattern|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
126
|
+
# parse a name and version requirement value from the pattern
|
127
|
+
name, requirement = pattern.rpartition("@").values_at(0, 2).map(&:strip)
|
128
|
+
|
129
|
+
if name == ""
|
130
|
+
# if name == "", then the pattern doesn't contain a valid version value.
|
131
|
+
# treat the entire pattern as the dependency name with no version.
|
132
|
+
name = pattern
|
133
|
+
requirement = nil
|
134
|
+
elsif !requirement.to_s.empty?
|
135
|
+
# check if the version requirement is a valid Gem::Requirement
|
136
|
+
# for range matching
|
137
|
+
requirements = requirement.split(",").map(&:strip)
|
138
|
+
if requirements.all? { |r| Gem::Requirement::PATTERN.match?(r) }
|
139
|
+
requirement = Gem::Requirement.new(requirements)
|
140
|
+
end
|
120
141
|
end
|
121
142
|
|
122
|
-
|
143
|
+
# the pattern's name must match the dependency's name
|
144
|
+
next false unless File.fnmatch?(name, dependency["name"], File::FNM_PATHNAME | File::FNM_CASEFOLD)
|
145
|
+
|
146
|
+
# if there is no version requirement configured or if the dependency doesn't have a version
|
147
|
+
# specified, return a value based on whether a version match is required
|
148
|
+
next !require_version if requirement.nil? || dependency["version"].to_s.empty?
|
149
|
+
|
150
|
+
case requirement
|
151
|
+
when String
|
152
|
+
# string match the requirement against "*" or the dependency's version
|
153
|
+
[ANY_VERSION_REQUIREMENT, dependency["version"]].any? { |r| requirement == r }
|
154
|
+
when Gem::Requirement
|
155
|
+
# if the version was parsed as a gem requirement, check whether the version requirement
|
156
|
+
# matches the dependency's version
|
157
|
+
Gem::Version.correct?(dependency["version"]) && requirement.satisfied_by?(Gem::Version.new(dependency["version"]))
|
158
|
+
end
|
123
159
|
end
|
124
160
|
end
|
125
161
|
|
data/lib/licensed/dependency.rb
CHANGED
@@ -9,6 +9,7 @@ module Licensed
|
|
9
9
|
attr_reader :version
|
10
10
|
attr_reader :errors
|
11
11
|
attr_reader :path
|
12
|
+
attr_reader :additional_terms
|
12
13
|
|
13
14
|
# Create a new project dependency
|
14
15
|
#
|
@@ -28,6 +29,7 @@ module Licensed
|
|
28
29
|
@errors = errors
|
29
30
|
path = path.to_s
|
30
31
|
@path = path
|
32
|
+
@additional_terms = []
|
31
33
|
|
32
34
|
# enforcing absolute paths makes life much easier when determining
|
33
35
|
# an absolute file path in #notices
|
@@ -80,6 +82,13 @@ module Licensed
|
|
80
82
|
files.compact
|
81
83
|
end
|
82
84
|
|
85
|
+
|
86
|
+
# Override the behavior of Licensee::Projects::FSProject#project_files to include
|
87
|
+
# additional license terms
|
88
|
+
def project_files
|
89
|
+
super + additional_license_terms_files
|
90
|
+
end
|
91
|
+
|
83
92
|
# Returns legal notices found at the dependency path
|
84
93
|
def notice_contents
|
85
94
|
Dir.glob(dir_path.join("*"))
|
@@ -90,6 +99,17 @@ module Licensed
|
|
90
99
|
.select { |notice| notice["text"].length > 0 } # files with content only
|
91
100
|
end
|
92
101
|
|
102
|
+
# Returns a hash of basic metadata about the dependency - name, version, type, etc
|
103
|
+
def metadata
|
104
|
+
{
|
105
|
+
# can be overriden by values in @metadata
|
106
|
+
"name" => name,
|
107
|
+
"version" => version
|
108
|
+
}.merge(
|
109
|
+
@metadata
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
93
113
|
private
|
94
114
|
|
95
115
|
def read_file_with_encoding_check(file_path)
|
@@ -102,6 +122,7 @@ module Licensed
|
|
102
122
|
def license_content_sources(files)
|
103
123
|
paths = Array(files).map do |file|
|
104
124
|
next file[:uri] if file[:uri]
|
125
|
+
next file[:source] if file[:source]
|
105
126
|
|
106
127
|
path = dir_path.join(file[:dir], file[:name])
|
107
128
|
normalize_source_path(path)
|
@@ -125,14 +146,8 @@ module Licensed
|
|
125
146
|
# Returns the metadata that represents this dependency. This metadata
|
126
147
|
# is written to YAML in the dependencys cached text file
|
127
148
|
def license_metadata
|
128
|
-
{
|
129
|
-
#
|
130
|
-
"name" => name,
|
131
|
-
"version" => version
|
132
|
-
}.merge(
|
133
|
-
@metadata
|
134
|
-
).merge({
|
135
|
-
# overrides all other values
|
149
|
+
metadata.merge({
|
150
|
+
# overrides all metadata values
|
136
151
|
"license" => license_key
|
137
152
|
})
|
138
153
|
end
|
@@ -157,5 +172,22 @@ module Licensed
|
|
157
172
|
"text" => text
|
158
173
|
}
|
159
174
|
end
|
175
|
+
|
176
|
+
# Returns an array of Licensee::ProjectFiles::LicenseFile created from
|
177
|
+
# this dependency's additional license terms
|
178
|
+
def additional_license_terms_files
|
179
|
+
@additional_license_terms_files ||= begin
|
180
|
+
files = additional_terms.map do |path|
|
181
|
+
next unless File.file?(path)
|
182
|
+
|
183
|
+
metadata = { dir: File.dirname(path), name: File.basename(path) }
|
184
|
+
Licensee::ProjectFiles::LicenseFile.new(
|
185
|
+
load_file(metadata),
|
186
|
+
{ source: "License terms loaded from #{metadata[:name]}" }
|
187
|
+
)
|
188
|
+
end
|
189
|
+
files.compact
|
190
|
+
end
|
191
|
+
end
|
160
192
|
end
|
161
193
|
end
|
@@ -9,7 +9,7 @@ require "fileutils"
|
|
9
9
|
module Licensed
|
10
10
|
module Sources
|
11
11
|
class Gradle < Source
|
12
|
-
DEFAULT_CONFIGURATIONS = ["
|
12
|
+
DEFAULT_CONFIGURATIONS = ["runtimeOnly", "runtimeClasspath"].freeze
|
13
13
|
GRADLE_LICENSES_PATH = ".gradle-licenses".freeze
|
14
14
|
GRADLE_LICENSES_CSV_NAME = "licenses.csv".freeze
|
15
15
|
class Dependency < Licensed::Dependency
|
@@ -46,7 +46,7 @@ module Licensed
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def enumerate_dependencies
|
49
|
-
JSON.parse(gradle_runner.run("printDependencies"
|
49
|
+
JSON.parse(gradle_runner.run("printDependencies")).map do |package|
|
50
50
|
name = "#{package['group']}:#{package['name']}"
|
51
51
|
Dependency.new(
|
52
52
|
name: name,
|
@@ -73,7 +73,7 @@ module Licensed
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def gradle_runner
|
76
|
-
@gradle_runner ||= Runner.new(
|
76
|
+
@gradle_runner ||= Runner.new(configurations, executable)
|
77
77
|
end
|
78
78
|
|
79
79
|
# Returns the configurations to include in license generation.
|
@@ -113,7 +113,7 @@ module Licensed
|
|
113
113
|
begin
|
114
114
|
# create the CSV file including dependency license urls using the gradle plugin
|
115
115
|
gradle_licenses_dir = File.join(config.root, GRADLE_LICENSES_PATH)
|
116
|
-
gradle_runner.run("generateLicenseReport"
|
116
|
+
gradle_runner.run("generateLicenseReport")
|
117
117
|
|
118
118
|
# parse the CSV report for dependency license urls
|
119
119
|
CSV.foreach(File.join(gradle_licenses_dir, GRADLE_LICENSES_CSV_NAME), headers: true).each_with_object({}) do |row, hsh|
|
@@ -134,80 +134,82 @@ module Licensed
|
|
134
134
|
# The Gradle::Runner class is a wrapper which provides
|
135
135
|
# an interface to run gradle commands with the init script initialized
|
136
136
|
class Runner
|
137
|
-
def initialize(
|
138
|
-
@root_path = root_path
|
137
|
+
def initialize(configurations, executable)
|
139
138
|
@executable = executable
|
140
|
-
@init_script = create_init_script(
|
139
|
+
@init_script = create_init_script(configurations)
|
141
140
|
end
|
142
141
|
|
143
|
-
def run(command
|
144
|
-
args = [
|
142
|
+
def run(command)
|
143
|
+
args = [command]
|
145
144
|
# The configuration cache is an incubating feature that can be activated manually.
|
146
145
|
# The gradle plugin for licenses does not support it so we prevent it to run for gradle version supporting it.
|
147
|
-
args << "--no-configuration-cache" if gradle_version >= "6.6"
|
146
|
+
args << "--no-configuration-cache" if gradle_version >= Gem::Version.new("6.6")
|
148
147
|
Licensed::Shell.execute(@executable, "-q", "--init-script", @init_script.path, *args)
|
149
148
|
end
|
150
149
|
|
151
150
|
private
|
152
151
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
}
|
171
|
-
}
|
172
|
-
dependencies {
|
173
|
-
classpath "com.github.jk1:gradle-license-report:#{gradle_version >= "7.0" ? "2.0" : "1.17"}"
|
152
|
+
def create_init_script(configurations)
|
153
|
+
# we need to create extensions in the event that the user hasn't configured custom configurations
|
154
|
+
# to avoid hitting errors where core Gradle configurations are set with canBeResolved=false
|
155
|
+
configuration_map = configurations.map { |c| [c, "licensed#{c}"] }.to_h
|
156
|
+
configuration_dsl = configuration_map.map { |orig, custom| "#{custom}.extendsFrom(#{orig})" }
|
157
|
+
|
158
|
+
f = Tempfile.new(["init", ".gradle"])
|
159
|
+
f.write(
|
160
|
+
<<~EOF
|
161
|
+
import com.github.jk1.license.render.CsvReportRenderer
|
162
|
+
import com.github.jk1.license.filter.LicenseBundleNormalizer
|
163
|
+
final configs = #{configuration_map.values.inspect}
|
164
|
+
|
165
|
+
initscript {
|
166
|
+
repositories {
|
167
|
+
maven {
|
168
|
+
url "https://plugins.gradle.org/m2/"
|
174
169
|
}
|
175
170
|
}
|
171
|
+
dependencies {
|
172
|
+
classpath "com.github.jk1:gradle-license-report:#{gradle_version >= Gem::Version.new("7.0") ? "2.0" : "1.17"}"
|
173
|
+
}
|
174
|
+
}
|
176
175
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
176
|
+
allprojects {
|
177
|
+
configurations {
|
178
|
+
#{configuration_dsl.join("\n") }
|
179
|
+
}
|
180
|
+
|
181
|
+
apply plugin: com.github.jk1.license.LicenseReportPlugin
|
182
|
+
licenseReport {
|
183
|
+
outputDir = "$rootDir/#{GRADLE_LICENSES_PATH}"
|
184
|
+
configurations = configs
|
185
|
+
renderers = [new CsvReportRenderer()]
|
186
|
+
filters = [new LicenseBundleNormalizer()]
|
187
|
+
}
|
185
188
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
}
|
189
|
+
task printDependencies {
|
190
|
+
doLast {
|
191
|
+
def dependencies = []
|
192
|
+
configs.each {
|
193
|
+
configurations[it].resolvedConfiguration.resolvedArtifacts.each { artifact ->
|
194
|
+
def id = artifact.moduleVersion.id
|
195
|
+
dependencies << "{ \\"group\\": \\"${id.group}\\", \\"name\\": \\"${id.name}\\", \\"version\\": \\"${id.version}\\" }"
|
196
|
+
}
|
197
|
+
}
|
198
|
+
println "[${dependencies.join(", ")}]"
|
197
199
|
}
|
198
200
|
}
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
201
|
+
}
|
202
|
+
EOF
|
203
|
+
)
|
204
|
+
f.close
|
205
|
+
f
|
204
206
|
end
|
205
207
|
|
206
|
-
#
|
207
|
-
def
|
208
|
-
|
209
|
-
|
210
|
-
|
208
|
+
# Returns the version of gradle used during execution
|
209
|
+
def gradle_version
|
210
|
+
@gradle_version ||= begin
|
211
|
+
version = Licensed::Shell.execute(@executable, "--version").scan(/Gradle [\d+]\.[\d+]/).last.split(" ").last
|
212
|
+
Gem::Version.new(version)
|
211
213
|
end
|
212
214
|
end
|
213
215
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Licensed
|
5
|
+
module Sources
|
6
|
+
class PNPM < Source
|
7
|
+
# The PNPM source requires matching reviewed or ignored dependencies
|
8
|
+
# on both name and version
|
9
|
+
def self.require_matched_dependency_version
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns true when pnpm is installed and a pnpm-lock.yaml file is found,
|
14
|
+
# otherwise false
|
15
|
+
def enabled?
|
16
|
+
return false unless Licensed::Shell.tool_available?("pnpm")
|
17
|
+
File.exist?(File.join(config.pwd, "pnpm-lock.yaml"))
|
18
|
+
end
|
19
|
+
|
20
|
+
def enumerate_dependencies
|
21
|
+
packages.map do |package|
|
22
|
+
name_with_version = "#{package["name"]}@#{package["version"]}"
|
23
|
+
Dependency.new(
|
24
|
+
name: name_with_version,
|
25
|
+
version: package["version"],
|
26
|
+
path: package["path"],
|
27
|
+
metadata: {
|
28
|
+
"type" => PNPM.type,
|
29
|
+
"name" => package["name"],
|
30
|
+
"summary" => package["description"],
|
31
|
+
"homepage" => package["homepage"]
|
32
|
+
}
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns package metadata returned from `pnpm licensed list`
|
38
|
+
def packages
|
39
|
+
JSON.parse(package_metadata_command).values.flatten
|
40
|
+
rescue JSON::ParserError => e
|
41
|
+
message = "Licensed was unable to parse the output from 'pnpm licenses list'. JSON Error: #{e.message}"
|
42
|
+
raise Licensed::Sources::Source::Error, message
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the output from running `pnpm licenses list` to get package metadata
|
46
|
+
def package_metadata_command
|
47
|
+
args = %w(--json --long)
|
48
|
+
args << "--prod" unless include_non_production?
|
49
|
+
Licensed::Shell.execute("pnpm", "licenses", "list", *args, allow_failure: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns whether to include non production dependencies based on the licensed configuration settings
|
53
|
+
def include_non_production?
|
54
|
+
config.dig("pnpm", "production_only") == false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -51,6 +51,12 @@ module Licensed
|
|
51
51
|
.downcase
|
52
52
|
.split("::")
|
53
53
|
end
|
54
|
+
|
55
|
+
# Returns true if the source requires matching reviewed and ignored dependencies'
|
56
|
+
# versions as well as their name
|
57
|
+
def require_matched_dependency_version
|
58
|
+
false
|
59
|
+
end
|
54
60
|
end
|
55
61
|
|
56
62
|
# all sources have a configuration
|
@@ -69,7 +75,9 @@ module Licensed
|
|
69
75
|
# Returns all dependencies that should be evaluated.
|
70
76
|
# Excludes ignored dependencies.
|
71
77
|
def dependencies
|
72
|
-
cached_dependencies
|
78
|
+
cached_dependencies
|
79
|
+
.reject { |d| ignored?(d) }
|
80
|
+
.each { |d| add_additional_terms_from_configuration(d) }
|
73
81
|
end
|
74
82
|
|
75
83
|
# Enumerate all source dependencies. Must be implemented by each source class.
|
@@ -79,7 +87,7 @@ module Licensed
|
|
79
87
|
|
80
88
|
# Returns whether a dependency is ignored in the configuration.
|
81
89
|
def ignored?(dependency)
|
82
|
-
config.ignored?(
|
90
|
+
config.ignored?(dependency.metadata, require_version: self.class.require_matched_dependency_version)
|
83
91
|
end
|
84
92
|
|
85
93
|
private
|
@@ -88,6 +96,11 @@ module Licensed
|
|
88
96
|
def cached_dependencies
|
89
97
|
@dependencies ||= enumerate_dependencies.compact
|
90
98
|
end
|
99
|
+
|
100
|
+
# Add any additional_terms for this dependency that have been added to the configuration
|
101
|
+
def add_additional_terms_from_configuration(dependency)
|
102
|
+
dependency.additional_terms.concat config.additional_terms_for_dependency("type" => self.class.type, "name" => dependency.name)
|
103
|
+
end
|
91
104
|
end
|
92
105
|
end
|
93
106
|
end
|
data/lib/licensed/sources.rb
CHANGED
@@ -6,19 +6,20 @@ module Licensed
|
|
6
6
|
require "licensed/sources/bundler"
|
7
7
|
require "licensed/sources/cabal"
|
8
8
|
require "licensed/sources/cargo"
|
9
|
+
require "licensed/sources/cocoapods"
|
9
10
|
require "licensed/sources/composer"
|
10
11
|
require "licensed/sources/dep"
|
11
12
|
require "licensed/sources/git_submodule"
|
12
13
|
require "licensed/sources/go"
|
14
|
+
require "licensed/sources/gradle"
|
13
15
|
require "licensed/sources/manifest"
|
16
|
+
require "licensed/sources/mix"
|
14
17
|
require "licensed/sources/npm"
|
15
18
|
require "licensed/sources/nuget"
|
16
19
|
require "licensed/sources/pip"
|
17
20
|
require "licensed/sources/pipenv"
|
21
|
+
require "licensed/sources/pnpm"
|
18
22
|
require "licensed/sources/swift"
|
19
|
-
require "licensed/sources/gradle"
|
20
|
-
require "licensed/sources/mix"
|
21
23
|
require "licensed/sources/yarn"
|
22
|
-
require "licensed/sources/cocoapods"
|
23
24
|
end
|
24
25
|
end
|
data/lib/licensed/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: licensed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0
|
4
|
+
version: 4.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: licensee
|
@@ -219,6 +219,7 @@ files:
|
|
219
219
|
- docs/commands/version.md
|
220
220
|
- docs/configuration.md
|
221
221
|
- docs/configuration/README.md
|
222
|
+
- docs/configuration/additional_terms.md
|
222
223
|
- docs/configuration/allowed_licenses.md
|
223
224
|
- docs/configuration/application_name.md
|
224
225
|
- docs/configuration/application_source.md
|
@@ -249,6 +250,7 @@ files:
|
|
249
250
|
- docs/sources/nuget.md
|
250
251
|
- docs/sources/pip.md
|
251
252
|
- docs/sources/pipenv.md
|
253
|
+
- docs/sources/pnpm.md
|
252
254
|
- docs/sources/stack.md
|
253
255
|
- docs/sources/swift.md
|
254
256
|
- docs/sources/yarn.md
|
@@ -298,6 +300,7 @@ files:
|
|
298
300
|
- lib/licensed/sources/nuget.rb
|
299
301
|
- lib/licensed/sources/pip.rb
|
300
302
|
- lib/licensed/sources/pipenv.rb
|
303
|
+
- lib/licensed/sources/pnpm.rb
|
301
304
|
- lib/licensed/sources/source.rb
|
302
305
|
- lib/licensed/sources/swift.rb
|
303
306
|
- lib/licensed/sources/yarn.rb
|