licensed 4.1.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7606d0b5e5f3755ee329a1963cda970c021deab08680c619731ed6fb3ba547da
4
- data.tar.gz: 668a2d87d8019284b6ce02bccdda851ad186f03cc7d389fbdd659473affc08cf
3
+ metadata.gz: 8fbd6fc2c6122a9b41d7ac258d08861114f6425df01f7e6fd0f482e03a9d3efb
4
+ data.tar.gz: 292fce45466f23cc690e3ffd8db6464928b80581c0cc99ab54dedf1e130b0adb
5
5
  SHA512:
6
- metadata.gz: '064129baadae7345b5c05e2635cc1850b8ce8321f1e2df803b5fc6d6704556a1337c7d1561024775801cf5cb4158b6c25657b06a0a9baf5ccac7a7453f35fa53'
7
- data.tar.gz: b3c6ba7179d7b777665f29b5cace4536181a428a5995c5e7d1d168b4ba6012fd333d191eb6ce23ab7b971a9cb8231dbfb999743c72bf91a1efe78ddec78223b7
6
+ metadata.gz: 3b462f9f482e65519349ad62b8c8cb53cbadd693dd8a0449e730c6aad63bf5484c78e39b5a67d61aa68169e5bcd65ef3131a591a0bd5965d8fcb0962d382e609
7
+ data.tar.gz: 399c6ff21a5a02c849e122365ccf4f8e5813bc7be628236fd5402894d012677501c9dd729cfbb597566f9fb4425f36360df59de8177b5cc0cb456c116e318916
data/CHANGELOG.md CHANGED
@@ -6,6 +6,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 4.3.0
10
+
11
+ ### Added
12
+
13
+ - Cocoapods support has been re-enabled using a cocoapods plugin (https://github.com/github/licensed/pull/644)
14
+
15
+ ## 4.2.0
16
+
17
+ ### Added
18
+
19
+ - Reviewed and ignored configuration lists support matching on versions and version ranges (https://github.com/github/licensed/pull/629)
20
+
21
+ ### Fixed
22
+
23
+ - Licensed should more reliably source dependencies from Gradle >= 8.0 (https://github.com/github/licensed/pull/630)
24
+
9
25
  ## 4.1.0
10
26
 
11
27
  ### Added
@@ -713,4 +729,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
713
729
 
714
730
  Initial release :tada:
715
731
 
716
- [Unreleased]: https://github.com/github/licensed/compare/4.1.0...HEAD
732
+ [Unreleased]: https://github.com/github/licensed/compare/4.3.0...HEAD
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- licensed (4.1.0)
4
+ licensed (4.3.0)
5
5
  json (~> 2.6)
6
6
  licensee (~> 9.16)
7
7
  parallel (~> 1.22)
@@ -14,7 +14,7 @@ PATH
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
- activesupport (7.0.4.2)
17
+ activesupport (7.0.4.3)
18
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
19
19
  i18n (>= 1.6, < 2)
20
20
  minitest (>= 5.1)
@@ -23,7 +23,7 @@ GEM
23
23
  public_suffix (>= 2.0.2, < 6.0)
24
24
  ast (2.4.2)
25
25
  byebug (11.1.3)
26
- concurrent-ruby (1.2.0)
26
+ concurrent-ruby (1.2.2)
27
27
  dotenv (2.8.1)
28
28
  faraday (2.7.4)
29
29
  faraday-net_http (>= 2.0, < 3.1)
@@ -39,13 +39,15 @@ GEM
39
39
  rugged (>= 0.24, < 2.0)
40
40
  thor (>= 0.19, < 2.0)
41
41
  mini_portile2 (2.8.1)
42
- minitest (5.17.0)
42
+ minitest (5.18.0)
43
+ minitest-hooks (1.5.0)
44
+ minitest (> 5.3)
43
45
  mocha (2.0.2)
44
46
  ruby2_keywords (>= 0.0.5)
45
- nokogiri (1.14.0)
47
+ nokogiri (1.14.2)
46
48
  mini_portile2 (~> 2.8.0)
47
49
  racc (~> 1.4)
48
- octokit (6.0.1)
50
+ octokit (6.1.0)
49
51
  faraday (>= 1, < 3)
50
52
  sawyer (~> 0.9)
51
53
  parallel (1.22.1)
@@ -54,14 +56,14 @@ GEM
54
56
  pathname-common_prefix (0.0.1)
55
57
  public_suffix (5.0.1)
56
58
  racc (1.6.2)
57
- rack (3.0.4.1)
59
+ rack (3.0.7)
58
60
  rainbow (3.1.1)
59
61
  rake (13.0.6)
60
62
  regexp_parser (2.6.2)
61
63
  reverse_markdown (2.1.1)
62
64
  nokogiri
63
65
  rexml (3.2.5)
64
- rubocop (1.44.1)
66
+ rubocop (1.45.1)
65
67
  json (~> 2.3)
66
68
  parallel (~> 1.10)
67
69
  parser (>= 3.2.0.0)
@@ -80,7 +82,7 @@ GEM
80
82
  rubocop-performance (1.15.2)
81
83
  rubocop (>= 1.7.0, < 2.0)
82
84
  rubocop-ast (>= 0.4.0)
83
- rubocop-rails (2.17.4)
85
+ rubocop-rails (2.18.0)
84
86
  activesupport (>= 4.2.0)
85
87
  rack (>= 1.1)
86
88
  rubocop (>= 1.33.0, < 2.0)
@@ -93,7 +95,7 @@ GEM
93
95
  faraday (>= 0.17.3, < 3)
94
96
  thor (1.2.1)
95
97
  tomlrb (2.0.3)
96
- tzinfo (2.0.5)
98
+ tzinfo (2.0.6)
97
99
  concurrent-ruby (~> 1.0)
98
100
  unicode-display_width (2.4.2)
99
101
 
@@ -104,6 +106,7 @@ DEPENDENCIES
104
106
  byebug (~> 11.1)
105
107
  licensed!
106
108
  minitest (~> 5.17)
109
+ minitest-hooks (~> 1.5)
107
110
  mocha (~> 2.0)
108
111
  rake (~> 13.0)
109
112
  rubocop-github (~> 0.20)
data/README.md CHANGED
@@ -47,7 +47,7 @@ sudo apt-get install cmake pkg-config
47
47
  brew install cmake pkg-config
48
48
  ```
49
49
 
50
- ### With a Gemfile
50
+ ### With Gemfile
51
51
 
52
52
  Add this line to your application's Gemfile:
53
53
 
@@ -61,7 +61,7 @@ And then execute:
61
61
  $> bundle
62
62
  ```
63
63
 
64
- ### With a Homebrew (on macOS)
64
+ ### With Homebrew (on macOS)
65
65
 
66
66
  ```bash
67
67
  brew install licensed
@@ -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
@@ -1,10 +1,8 @@
1
1
  # CocoaPods
2
2
 
3
- **NOTE!**: Enumerating Cocoapods dependencies is disabled until the cocoapods-core gem is compatible with Rails 7+. See https://github.com/CocoaPods/Core/pull/733
3
+ The cocoapods source will detect dependencies when `Podfile` and `Podfile.lock` are found at an app's `source_path`. The cocoapods source uses the [cocoapods-dependencies-list](https://github.com/jonabc/cocoapods-dependencies-list) plugin to enumerate dependencies and gather metadata on each package.
4
4
 
5
- The cocoapods source will detect dependencies when `Podfile` and `Podfile.lock` are found at an app's `source_path`.
6
-
7
- It uses the `pod` CLI commands to enumerate dependencies and gather metadata on each package.
5
+ **NOTE: Licensed does not install the [cocoapods-dependencies-list](https://github.com/jonanc/cocoapods-dependencies-list) plugin. Users must install the gem alongside the cocoapods gem to enumerate cocoapods dependencies.**
8
6
 
9
7
  ## Evaluating dependencies from a specific target
10
8
 
@@ -15,3 +13,12 @@ cocoapods:
15
13
  targets:
16
14
  - ios
17
15
  ```
16
+
17
+ ## Specifying which pod executable to run
18
+
19
+ The cocoapods source will call the `pod` executable to evaluate dependencies by default. If needed, you can override the executable used with the `cocoapods.command` configuration option. This might be useful if the full path to the `pod` executable is needed (e.g. `pod` is not findable from the system `PATH`), or if you need to execute `pod` with `bundle exec`.
20
+
21
+ ```yml
22
+ cocoapods:
23
+ command: 'bundle exec pod'
24
+ ```
data/docs/sources/pnpm.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
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
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
+
5
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).
6
8
 
7
9
  ## Including development dependencies
@@ -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
- return false if app.reviewed?(record, match_version: data_source == "configuration")
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, match_version: false)
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, match_version: false)
77
- any_list_pattern_matched? self["reviewed"][dependency["type"]], dependency, match_version: match_version
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
- (self["ignored"][dependency["type"]] ||= []) << dependency["name"]
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
@@ -112,20 +116,46 @@ module Licensed
112
116
  # Returns an array of paths to files containing additional license terms.
113
117
  def additional_terms_for_dependency(dependency)
114
118
  amendment_paths = Array(self.dig("additional_terms", dependency["type"], dependency["name"]))
115
- amendment_paths.flat_map { |path| Dir.glob(self.root.join(path)) }
119
+ amendment_paths.flat_map { |path| Dir.glob(self.root.join(path)) }.sort
116
120
  end
117
121
 
118
122
  private
119
123
 
120
- def any_list_pattern_matched?(list, dependency, match_version: false)
124
+ def any_list_pattern_matched?(list, dependency, require_version: false)
121
125
  Array(list).any? do |pattern|
122
- if match_version
123
- at_version = "@#{dependency["version"]}"
124
- pattern, pattern_version = pattern.rpartition(at_version).values_at(0, 1)
125
- next false if pattern == "" || pattern_version == ""
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
126
141
  end
127
142
 
128
- File.fnmatch?(pattern, dependency["name"], File::FNM_PATHNAME | File::FNM_CASEFOLD)
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
129
159
  end
130
160
  end
131
161
 
@@ -99,6 +99,17 @@ module Licensed
99
99
  .select { |notice| notice["text"].length > 0 } # files with content only
100
100
  end
101
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
+
102
113
  private
103
114
 
104
115
  def read_file_with_encoding_check(file_path)
@@ -135,14 +146,8 @@ module Licensed
135
146
  # Returns the metadata that represents this dependency. This metadata
136
147
  # is written to YAML in the dependencys cached text file
137
148
  def license_metadata
138
- {
139
- # can be overriden by values in @metadata
140
- "name" => name,
141
- "version" => version
142
- }.merge(
143
- @metadata
144
- ).merge({
145
- # overrides all other values
149
+ metadata.merge({
150
+ # overrides all metadata values
146
151
  "license" => license_key
147
152
  })
148
153
  end
@@ -3,32 +3,29 @@ require "json"
3
3
  require "pathname"
4
4
  require "uri"
5
5
 
6
- # **NOTE** Cocoapods is disabled until cocoapods-core supports recent rails versions
7
- # https://github.com/CocoaPods/Core/pull/733
8
- # require "cocoapods-core"
9
-
10
6
  module Licensed
11
7
  module Sources
12
8
  class Cocoapods < Source
13
- def enabled?
14
- false
9
+ DEFAULT_POD_COMMAND = "pod".freeze
10
+ MISSING_PLUGIN_MESSAGE = "Error running `pods dependencies`. Please ensure the cocoapods-dependencies-list gem is installed, it is required for licensed to enumerate dependencies.".freeze
15
11
 
16
- # return unless Licensed::Shell.tool_available?("pod")
12
+ def enabled?
13
+ return unless Licensed::Shell.tool_available?("pod")
17
14
 
18
- # config.pwd.join("Podfile").exist? && config.pwd.join("Podfile.lock").exist?
15
+ config.pwd.join("Podfile").exist? && config.pwd.join("Podfile.lock").exist?
19
16
  end
20
17
 
21
18
  def enumerate_dependencies
22
19
  pods.map do |pod|
23
- name = pod.name
24
- path = dependency_path(pod.root_name)
25
- version = lockfile.version(name).version
26
-
27
20
  Dependency.new(
28
- path: path,
29
- name: name,
30
- version: version,
31
- metadata: { "type" => Cocoapods.type }
21
+ name: pod["name"],
22
+ version: pod["version"],
23
+ path: pod["path"],
24
+ metadata: {
25
+ "type" => Cocoapods.type,
26
+ "summary" => pod["summary"],
27
+ "homepage" => pod["homepage"]
28
+ }
32
29
  )
33
30
  end
34
31
  end
@@ -36,32 +33,32 @@ module Licensed
36
33
  private
37
34
 
38
35
  def pods
39
- return lockfile.dependencies if targets.nil?
40
-
41
- targets_to_validate = podfile.target_definition_list.filter { |t| targets.include?(t.label) }
42
- if targets_to_validate.any?
43
- targets_to_validate.map(&:dependencies).flatten
44
- else
45
- raise Licensed::Sources::Source::Error, "Unable to find any target in the Podfile matching the ones provided in the config."
46
- end
36
+ cocoapods_dependencies_json.values.flatten
47
37
  end
48
38
 
49
- def targets
50
- @targets ||= config.dig("cocoapods", "targets")&.map { |t| "Pods-#{t}" }
51
- end
39
+ def cocoapods_dependencies_json
40
+ args = ["dependencies", "--include-path"]
41
+ args << "--targets=#{targets.join(",")}" if targets.any?
52
42
 
53
- def lockfile
54
- @lockfile = nil
55
- # @lockfile ||= Pod::Lockfile.from_file(config.pwd.join("Podfile.lock"))
43
+ output = Licensed::Shell.execute(*pod_command, *args, allow_failure: true)
44
+ if output.include? "Unknown command"
45
+ raise Licensed::Sources::Source::Error, MISSING_PLUGIN_MESSAGE
46
+ end
47
+
48
+ JSON.parse(output)
49
+ rescue JSON::ParserError => e
50
+ message = "Licensed was unable to parse the output from 'pod dependencies'. JSON Error: #{e.message}"
51
+ raise Licensed::Sources::Source::Error, message
56
52
  end
57
53
 
58
- def podfile
59
- @podfile = nil
60
- # @podfile ||= Pod::Podfile.from_file(config.pwd.join("Podfile"))
54
+ def targets
55
+ return [] unless [String, Array].any? { |type| source_config["targets"].is_a?(type) }
56
+ Array(source_config["targets"]).map { |t| "Pods-#{t}" }
61
57
  end
62
58
 
63
- def dependency_path(name)
64
- config.pwd.join("Pods/#{name}")
59
+ def pod_command
60
+ return DEFAULT_POD_COMMAND unless source_config["command"].is_a?(String)
61
+ source_config["command"].split
65
62
  end
66
63
  end
67
64
  end
@@ -9,7 +9,7 @@ require "fileutils"
9
9
  module Licensed
10
10
  module Sources
11
11
  class Gradle < Source
12
- DEFAULT_CONFIGURATIONS = ["runtime", "runtimeClasspath"].freeze
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", config.source_path)).map do |package|
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(config.pwd, configurations, executable)
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", config.source_path)
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(root_path, configurations, executable)
138
- @root_path = root_path
137
+ def initialize(configurations, executable)
139
138
  @executable = executable
140
- @init_script = create_init_script(root_path, configurations)
139
+ @init_script = create_init_script(configurations)
141
140
  end
142
141
 
143
- def run(command, source_path)
144
- args = [format_command(command, source_path)]
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 gradle_version
154
- @gradle_version ||= Licensed::Shell.execute(@executable, "--version").scan(/Gradle [\d+]\.[\d+]/).last.split(" ").last
155
- end
156
-
157
- def create_init_script(path, configurations)
158
- Dir.chdir(path) do
159
- f = Tempfile.new(["init", ".gradle"], @root_path)
160
- f.write(
161
- <<~EOF
162
- import com.github.jk1.license.render.CsvReportRenderer
163
- import com.github.jk1.license.filter.LicenseBundleNormalizer
164
- final configs = #{configurations.inspect}
165
-
166
- initscript {
167
- repositories {
168
- maven {
169
- url "https://plugins.gradle.org/m2/"
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
- allprojects {
178
- apply plugin: com.github.jk1.license.LicenseReportPlugin
179
- licenseReport {
180
- outputDir = "$rootDir/.gradle-licenses"
181
- configurations = configs
182
- renderers = [new CsvReportRenderer()]
183
- filters = [new LicenseBundleNormalizer()]
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
- task printDependencies {
187
- doLast {
188
- def dependencies = []
189
- configs.each {
190
- configurations[it].resolvedConfiguration.resolvedArtifacts.each { artifact ->
191
- def id = artifact.moduleVersion.id
192
- dependencies << "{ \\"group\\": \\"${id.group}\\", \\"name\\": \\"${id.name}\\", \\"version\\": \\"${id.version}\\" }"
193
- }
194
- }
195
- println "[${dependencies.join(", ")}]"
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
- EOF
200
- )
201
- f.close
202
- f
203
- end
201
+ }
202
+ EOF
203
+ )
204
+ f.close
205
+ f
204
206
  end
205
207
 
206
- # Prefixes the gradle command with the project name for multi-build projects.
207
- def format_command(command, source_path)
208
- Dir.chdir(source_path) do
209
- path = Licensed::Shell.execute(@executable, "properties", "-Dorg.gradle.logging.level=quiet").scan(/path:.*/).last.split(" ").last
210
- path == ":" ? command : "#{path}:#{command}"
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
@@ -4,6 +4,12 @@ require "json"
4
4
  module Licensed
5
5
  module Sources
6
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
+
7
13
  # Returns true when pnpm is installed and a pnpm-lock.yaml file is found,
8
14
  # otherwise false
9
15
  def enabled?
@@ -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
@@ -81,7 +87,12 @@ module Licensed
81
87
 
82
88
  # Returns whether a dependency is ignored in the configuration.
83
89
  def ignored?(dependency)
84
- config.ignored?("type" => self.class.type, "name" => dependency.name)
90
+ config.ignored?(dependency.metadata, require_version: self.class.require_matched_dependency_version)
91
+ end
92
+
93
+ # Returns configuration options set for the current source
94
+ def source_config
95
+ @source_config ||= config[self.class.type].is_a?(Hash) ? config[self.class.type] : {}
85
96
  end
86
97
 
87
98
  private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Licensed
3
- VERSION = "4.1.0".freeze
3
+ VERSION = "4.3.0".freeze
4
4
 
5
5
  def self.previous_major_versions
6
6
  major_version = Gem::Version.new(Licensed::VERSION).segments.first
data/licensed.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.required_ruby_version = ">= 2.6.0"
24
+ spec.required_ruby_version = ">= 2.7.0"
25
25
 
26
26
  spec.add_dependency "licensee", "~> 9.16"
27
27
  spec.add_dependency "thor", "~> 1.2"
@@ -31,10 +31,10 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency "parallel", "~> 1.22"
32
32
  spec.add_dependency "reverse_markdown", "~> 2.1"
33
33
  spec.add_dependency "json", "~> 2.6"
34
- # spec.add_dependency "cocoapods-core", "~> 1.11"
35
34
 
36
35
  spec.add_development_dependency "rake", "~> 13.0"
37
36
  spec.add_development_dependency "minitest", "~> 5.17"
37
+ spec.add_development_dependency "minitest-hooks", "~> 1.5"
38
38
  spec.add_development_dependency "mocha", "~> 2.0"
39
39
  spec.add_development_dependency "rubocop-github", "~> 0.20"
40
40
  spec.add_development_dependency "byebug", "~> 11.1"
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.1.0
4
+ version: 4.3.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-02-08 00:00:00.000000000 Z
11
+ date: 2023-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: licensee
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
152
  version: '5.17'
153
+ - !ruby/object:Gem::Dependency
154
+ name: minitest-hooks
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.5'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.5'
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: mocha
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -321,7 +335,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
321
335
  requirements:
322
336
  - - ">="
323
337
  - !ruby/object:Gem::Version
324
- version: 2.6.0
338
+ version: 2.7.0
325
339
  required_rubygems_version: !ruby/object:Gem::Requirement
326
340
  requirements:
327
341
  - - ">="