licensed 3.5.0 → 3.6.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: d2fafd3b11ba6f63760979021e1628aa1d4ae0e0cd5b0b413b06c6163a1b64fd
4
- data.tar.gz: d5fe530e59e0091b44f0ae08403f72707f6dc4db391a6d7e39e4b620c01b1da9
3
+ metadata.gz: a328b5551bdf77593f4bf97f4a846b7792898b6f749c25f5c5f39e68669f2164
4
+ data.tar.gz: ac9b2013cf25d9dab94aadd2122a41bfa2790d741bd9e1588a270cd122dfaddb
5
5
  SHA512:
6
- metadata.gz: a2b691823b7cbc692fb155bc672772fea156932a3d7d38111634927a0babf427343f992eded7d219041575e64e698a9821fdf1e9e12390f8e29a2c95a42e340a
7
- data.tar.gz: 61872b213231ead8c7ce5d209af8332964e8562e8809dbbf7c3e5fd95b84cfffcc7b9c4391f0e81d6e094325293bbe61520458d4cc1233072f4b0c41b04cd165
6
+ metadata.gz: 846cadb01c2045ea258a785767ebfc8df3cee9d3a05648c93291c94cc21b7e84fb83146476c4afb64f9bc137a530cb84ba523ec41e2c4938396629b5b8901795
7
+ data.tar.gz: 4aa7028294894b9f0c1781b558032d80f87669e959e71aa1b635c7a8687f77b4cf11be6431f280cfcbc06ea25f605da061a9af2fb480ea134a4172f989060451
data/CHANGELOG.md CHANGED
@@ -6,6 +6,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 3.6.0
10
+
11
+ 2022-03-17
12
+
13
+ ### Added
14
+
15
+ - Composer dev dependencies can optionally be included in enumerated PHP dependencies (:tada: @digilist https://github.com/github/licensed/pull/486)
16
+ - Getting started usage documentation (https://github.com/github/licensed/pull/483)
17
+ - Initial support for NPM workspaces (https://github.com/github/licensed/pull/485)
18
+
19
+ ### Changed
20
+
21
+ - Transitive dependencies are now enumerated by the `pip` source (https://github.com/github/licensed/pull/480)
22
+
23
+ ### Fixed
24
+
25
+ - `licensed cache --force` will now correctly overwrite existing license classifications (https://github.com/github/licensed/pull/473)
26
+
9
27
  ## 3.5.0
10
28
 
11
29
  2022-02-24
@@ -579,4 +597,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
579
597
 
580
598
  Initial release :tada:
581
599
 
582
- [Unreleased]: https://github.com/github/licensed/compare/3.4.4...HEAD
600
+ [Unreleased]: https://github.com/github/licensed/compare/3.6.0...HEAD
data/README.md CHANGED
@@ -74,6 +74,8 @@ For system wide usage, install licensed to a location on `$PATH`, e.g. `/usr/loc
74
74
 
75
75
  ## Usage
76
76
 
77
+ See [getting started](./docs/getting_started.md) for guidance using Licensed as part of your developer workflow.
78
+
77
79
  ### Available commands
78
80
 
79
81
  See the [commands documentation](./docs/commands) for documentation on available commands, or run `licensed -h` to see all of the current available commands.
@@ -86,18 +88,6 @@ A configuration file is required for most commands. See the [configuration file
86
88
 
87
89
  Licensed can enumerate dependency for many languages, package managers, and frameworks. See the [sources documentation](./docs/sources) for the list of currently available sources. Sources can be explicitly enabled and disabled as a [configuration option](./docs/configuration/dependency_source_enumerators.md.md).
88
90
 
89
- ### Automation
90
-
91
- #### Bundler
92
-
93
- The [bundler-licensed plugin](https://github.com/sergey-alekseev/bundler-licensed) runs `licensed cache` automatically when using `bundler`. See the linked repo for usage and details.
94
-
95
- #### GitHub Actions
96
-
97
- The [licensed-ci](https://github.com/marketplace/actions/licensed-ci) GitHub Action runs `licensed` as part of an opinionated CI workflow and can be configured to run on any GitHub Action event. See the linked actions for usage and details.
98
-
99
- The [setup-licensed](https://github.com/marketplace/actions/setup-github-licensed) GitHub Action installs `licensed` to the workflow environment. See the linked actions for usage and details.
100
-
101
91
  ## Development
102
92
 
103
93
  To get started after checking out the repo, run
@@ -8,6 +8,6 @@ Licensed uses [Licensee](https://github.com/licensee/licensee) to detect and eva
8
8
  licensee:
9
9
  # the confidence threshold is an integer between 1 and 100. the value represents
10
10
  # the minimum percentage confidence that Licensee must have to report a matched license
11
- # https://github.com/licensee/licensee/blob/jonabc-patch-1/docs/customizing.md#adjusting-the-confidence-threshold
11
+ # https://github.com/licensee/licensee/blob/master/docs/customizing.md#adjusting-the-confidence-threshold
12
12
  confidence_threshold: 90 # default value: 98
13
13
  ```
@@ -0,0 +1,40 @@
1
+ # Getting Started
2
+
3
+ Licensed's core workflow is a multi-step process:
4
+
5
+ 1. `licensed cache` ([docs](./commands/cache.md)) is run manually and/or in an automated workflow
6
+ - Creates or updates files in a git repo containing metadata including licenses and other legal text for each dependency used by a project
7
+ 1. `licensed status` ([docs](./commands/status.md)) is run manually and/or in an automated workflow
8
+ - Validate that every detected dependency has a metadata file written in the repository, and that each dependency's stored metadata passes a number of compliance checks
9
+ 1. Any detected errors/warnings are manually [resolved](./commands/status.md#status-errors-and-resolutions)
10
+ 1. Repeat the above steps until all dependencies have metadata files stored in the repository and `licensed status` is not reporting any errors.
11
+
12
+ ## Caching dependency metadata
13
+
14
+ Caching depedency metadata into the repository brings the metadata contents closer to the dependencies where they are used, making status validation faster and possible in offline scenarios. Keeping metadata alongside your code in git gives teams an easily auditable trail for dependency updates over time, and ties into common review practices to ensure that changes aren't quietly ignored.
15
+
16
+ Caching metadata should be done whenever project code changes, to ensure that metadata files are in sync with the current state of the project code.
17
+
18
+ ## Checking dependency metadata status
19
+
20
+ Dependency metadata checks verify that every dependency
21
+
22
+ - has a metadata file available, or has been explicitly ignored by the project owners or OSS experts
23
+ - is using an approved OSS license, or has been reviewed and signed off by an OSS expert
24
+ - is up to date with the current state of a project
25
+
26
+ Checking dependencies for compliance violations should be performed whenever code changes in a repository. Moving compliance checks inline in the development workflow reduces friction later, and can even prevent costly situations later if a non-compliant dependency would need to be removed from a project.
27
+
28
+ ## Automated workflows
29
+
30
+ Integrating github/licensed into your workflow can be tedious, and luckily there are a few automated tools available to make usage easier.
31
+
32
+ ### Bundler
33
+
34
+ The [bundler-licensed plugin](https://github.com/sergey-alekseev/bundler-licensed) runs `licensed cache` automatically when using `bundler`. See the linked repo for usage and details.
35
+
36
+ ### GitHub Actions
37
+
38
+ The [licensed-ci](https://github.com/marketplace/actions/licensed-ci) GitHub Action runs `licensed` as part of an opinionated CI workflow and can be configured to run on any GitHub Action event to automatically update the cached metadata files and check their status. See the linked action for usage and details.
39
+
40
+ The [setup-licensed](https://github.com/marketplace/actions/setup-github-licensed) GitHub Action installs `licensed` to the workflow environment. See the linked actions for usage and details.
@@ -12,3 +12,12 @@ The default composer application file location is `<repository root>/composer.ph
12
12
  composer:
13
13
  application_path: "/path/to/composer"
14
14
  ```
15
+
16
+ ### Dev dependencies
17
+
18
+ By default licensed ignores all dev dependencies. To consider dev dependencies as well, use the `composer.include_dev` configuration setting.
19
+
20
+ ```yml
21
+ composer:
22
+ include_dev: true
23
+ ```
data/docs/sources/npm.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The npm source will detect dependencies `package.json` is found at an apps `source_path`. It uses `npm list` to enumerate dependencies and metadata.
4
4
 
5
- ### Including development dependencies
5
+ ## Including development dependencies
6
6
 
7
7
  By default, the npm source will exclude all development dependencies. To include development or test dependencies, set `production_only: false` in the licensed configuration.
8
8
 
@@ -10,3 +10,13 @@ By default, the npm source will exclude all development dependencies. To include
10
10
  npm:
11
11
  production_only: false
12
12
  ```
13
+
14
+ ## Using licensed with npm workspaces
15
+
16
+ Licensed requires npm version 8.5.0 or later to enumerate dependencies inside of npm workspaces. For the best results, treat each workspace directory as a separate app `source_path`:
17
+
18
+ ```yml
19
+ apps:
20
+ - source_path: path/to/workspace/a
21
+ - source_path: path/to/workspace/b
22
+ ```
data/docs/sources/pip.md CHANGED
@@ -2,11 +2,6 @@
2
2
 
3
3
  The pip source uses `pip` CLI commands to enumerate dependencies and properties. It is expected that `pip` is available in the `virtual_env_dir` specific directory before running `licensed`.
4
4
 
5
- Your repository root should also contain a `requirements.txt` file which contains all the packages and dependences that are needed. You can generate one with `pip` using the command:
6
- ```
7
- pip freeze > requirements.txt
8
- ```
9
-
10
5
  A `virtualenv` directory is required before running `licensed`. You can setup a `virtualenv` by running the command:
11
6
  ```
12
7
  virtualenv <your_venv_dir>
@@ -82,15 +82,8 @@ module Licensed
82
82
  report["filename"] = filename.to_s
83
83
  report["version"] = dependency.version
84
84
 
85
- if options[:force] || save_dependency_record?(dependency, cached_record)
86
- if dependency.record.matches?(cached_record)
87
- # use the cached license value if the license text wasn't updated
88
- dependency.record["license"] = cached_record["license"]
89
- elsif cached_record && app.reviewed?(dependency.record)
90
- # if the license text changed and the dependency is set as reviewed
91
- # force a re-review of the dependency
92
- dependency.record["review_changed_license"] = true
93
- end
85
+ if save_dependency_record?(dependency, cached_record)
86
+ update_dependency_from_cached_record(app, dependency, cached_record)
94
87
 
95
88
  dependency.record.save(filename)
96
89
  report["cached"] = true
@@ -119,6 +112,7 @@ module Licensed
119
112
  # Returns true if dependency's record should be saved
120
113
  def save_dependency_record?(dependency, cached_record)
121
114
  return true if cached_record.nil?
115
+ return true if options[:force]
122
116
 
123
117
  cached_version = cached_record["version"]
124
118
  return true if cached_version.nil? || cached_version.empty?
@@ -126,6 +120,23 @@ module Licensed
126
120
  false
127
121
  end
128
122
 
123
+ # Update dependency metadata from the cached record, to support:
124
+ # 1. continuity between cache runs to cut down on churn
125
+ # 2. notifying users when changed content needs to be reviewed
126
+ def update_dependency_from_cached_record(app, dependency, cached_record)
127
+ return if cached_record.nil?
128
+ return if options[:force]
129
+
130
+ if dependency.record.matches?(cached_record)
131
+ # use the cached license value if the license text wasn't updated
132
+ dependency.record["license"] = cached_record["license"]
133
+ elsif app.reviewed?(dependency.record)
134
+ # if the license text changed and the dependency is set as reviewed
135
+ # force a re-review of the dependency
136
+ dependency.record["review_changed_license"] = true
137
+ end
138
+ end
139
+
129
140
  # Clean up cached files that dont match current dependencies
130
141
  #
131
142
  # Returns nothing
@@ -28,7 +28,10 @@ module Licensed
28
28
  end
29
29
 
30
30
  def packages
31
- JSON.parse(File.read(composer_lock))["packages"]
31
+ packages = JSON.parse(File.read(composer_lock))
32
+ return packages["packages"] unless include_dev?
33
+
34
+ packages["packages"] + packages["packages-dev"]
32
35
  end
33
36
 
34
37
  # Returns the output from running `php composer.phar` to get package metadata
@@ -56,6 +59,11 @@ module Licensed
56
59
  def composer_lock
57
60
  config.pwd.join("composer.lock")
58
61
  end
62
+
63
+ # Returns whether to include dev packages based on the licensed configuration settings
64
+ def include_dev?
65
+ config.dig("composer", "include_dev") == true
66
+ end
59
67
  end
60
68
  end
61
69
  end
@@ -24,11 +24,13 @@ module Licensed
24
24
  end
25
25
 
26
26
  def enabled?
27
- Licensed::Shell.tool_available?("npm") && File.exist?(config.pwd.join("package.json"))
27
+ Licensed::Shell.tool_available?("npm") && File.exist?(package_json_path)
28
28
  end
29
29
 
30
30
  def enumerate_dependencies
31
31
  packages.map do |name, package|
32
+ next if package["name"] == project_name
33
+
32
34
  errors = package["problems"] unless package["path"]
33
35
  Dependency.new(
34
36
  name: name,
@@ -159,6 +161,26 @@ module Licensed
159
161
  def extract_version(parent, name)
160
162
  parent&.dig("_dependencies", name) || peer_dependency(parent, name)
161
163
  end
164
+
165
+ # Returns the current projects name
166
+ def project_name
167
+ return unless package_json
168
+ package_json["name"]
169
+ end
170
+
171
+ ## Returns the parse package.json for the current project
172
+ def package_json
173
+ return unless File.exist?(package_json_path)
174
+
175
+ @package_json ||= JSON.parse(File.read(package_json_path))
176
+ rescue JSON::ParserError => e
177
+ message = "Licensed was unable to parse package.json. JSON Error: #{e.message}"
178
+ raise Licensed::Sources::Source::Error, message
179
+ end
180
+
181
+ def package_json_path
182
+ @package_json_path ||= File.join(config.pwd, "package.json")
183
+ end
162
184
  end
163
185
  end
164
186
  end
@@ -7,17 +7,14 @@ require "parallel"
7
7
  module Licensed
8
8
  module Sources
9
9
  class Pip < Source
10
- VERSION_OPERATORS = %w(< > <= >= == !=).freeze
11
- PACKAGE_REGEX = /^([\w\.-]+)(#{VERSION_OPERATORS.join("|")})?/
10
+ PACKAGE_INFO_SEPARATOR = "\n---\n"
12
11
 
13
12
  def enabled?
14
- return unless virtual_env_pip && Licensed::Shell.tool_available?(virtual_env_pip)
15
- File.exist?(config.pwd.join("requirements.txt"))
13
+ virtual_env_pip && Licensed::Shell.tool_available?(virtual_env_pip)
16
14
  end
17
15
 
18
16
  def enumerate_dependencies
19
- Parallel.map(packages_from_requirements_txt, in_threads: Parallel.processor_count) do |package_name|
20
- package = package_info(package_name)
17
+ packages.map do |package|
21
18
  location = File.join(package["Location"], package["Name"].gsub("-", "_") + "-" + package["Version"] + ".dist-info")
22
19
  Dependency.new(
23
20
  name: package["Name"],
@@ -34,25 +31,45 @@ module Licensed
34
31
 
35
32
  private
36
33
 
37
- def packages_from_requirements_txt
38
- File.read(config.pwd.join("requirements.txt"))
39
- .lines
40
- .reject { |line| line.include?("://") }
41
- .map { |line| line.strip.match(PACKAGE_REGEX) { |match| match.captures.first } }
42
- .compact
34
+ # Returns parsed information for all packages used by the project,
35
+ # using `pip list` to determine what packages are used and `pip show`
36
+ # to gather package information
37
+ def packages
38
+ all_packages = pip_show_command(package_names)
39
+ all_packages.split(PACKAGE_INFO_SEPARATOR).reduce([]) do |accum, val|
40
+ accum << parse_package_info(val)
41
+ end
42
+ end
43
+
44
+ # Returns the names of all of the packages used by the current project,
45
+ # as returned from `pip list`
46
+ def package_names
47
+ @package_names ||= begin
48
+ JSON.parse(pip_list_command).map { |package| package["name"] }
49
+ rescue JSON::ParserError => e
50
+ message = "Licensed was unable to parse the output from 'npm list'. JSON Error: #{e.message}"
51
+ raise Licensed::Sources::Source::Error, message
52
+ end
43
53
  end
44
54
 
45
- def package_info(package_name)
46
- p_info = pip_command(package_name).lines
47
- p_info.each_with_object(Hash.new(0)) { |pkg, a|
55
+ # Returns a hash filled with package info parsed from the email-header formatted output
56
+ # returned by `pip show`
57
+ def parse_package_info(package_info)
58
+ package_info.lines.each_with_object(Hash.new(0)) { |pkg, a|
48
59
  k, v = pkg.split(":", 2)
49
60
  next if k.nil? || k.empty?
50
61
  a[k.strip] = v&.strip
51
62
  }
52
63
  end
53
64
 
54
- def pip_command(*args)
55
- Licensed::Shell.execute(virtual_env_pip, "--disable-pip-version-check", "show", *args)
65
+ # Returns the output from `pip list --format=json`
66
+ def pip_list_command
67
+ Licensed::Shell.execute(virtual_env_pip, "--disable-pip-version-check", "list", "--format=json")
68
+ end
69
+
70
+ # Returns the output from `pip show <package> <package> ...`
71
+ def pip_show_command(packages)
72
+ Licensed::Shell.execute(virtual_env_pip, "--disable-pip-version-check", "show", *packages)
56
73
  end
57
74
 
58
75
  def virtual_env_pip
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Licensed
3
- VERSION = "3.5.0".freeze
3
+ VERSION = "3.6.0".freeze
4
4
 
5
5
  def self.previous_major_versions
6
6
  major_version = Gem::Version.new(Licensed::VERSION).segments.first
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: 3.5.0
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-24 00:00:00.000000000 Z
11
+ date: 2022-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: licensee
@@ -266,6 +266,7 @@ files:
266
266
  - docs/configuration/ignoring_dependencies.md
267
267
  - docs/configuration/metadata_cache.md
268
268
  - docs/configuration/reviewing_dependencies.md
269
+ - docs/getting_started.md
269
270
  - docs/migrations/v2.md
270
271
  - docs/migrations/v3.md
271
272
  - docs/packaging.md