Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow generating notice file from live computed data #572

Merged
merged 5 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .licenses/bundler/bundler.dep.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: bundler
version: 2.3.24
version: 2.3.25
type: bundler
summary: The best way to manage your application's dependencies
homepage: https://bundler.io
Expand Down
2 changes: 1 addition & 1 deletion .licenses/bundler/faraday-net_http.dep.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: faraday-net_http
version: 3.0.1
version: 3.0.2
type: bundler
summary: Faraday adapter for Net::HTTP
homepage: https://github.com/lostisland/faraday-net_http
Expand Down
4 changes: 3 additions & 1 deletion docs/commands/notices.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

Outputs license and notice text for all dependencies in each app into a `NOTICE` file in the app's `cache_path`. If an app uses a shared cache path, the file name will contain the app name as well, e.g. `NOTICE.my_app`.

`NOTICE` file contents are retrieved from cached records, with the assumption that cached records have already been reviewed in a compliance workflow.
`NOTICE` file contents are retrieved from cached records when the `--computed`/`-l` option is not set, with the assumption that cached records have already been reviewed in a compliance workflow. When the `--computed`/`-l` option is set and a dependency's license is not found, that dependency's license text will be empty in the `NOTICE` file.

## Options

- `--config`/`-c`: the path to the licensed configuration file
- default value: `./.licensed.yml`
- `--sources`/`-s`: runtime filter on which dependency sources are run. Sources must also be enabled in the licensed configuration file.
- default value: not set, all configured sources
- `--computed`/`-l`: use live computed when generating a `NOTICE` file
- default value: not set, `NOTICE` file generated from cached records
6 changes: 4 additions & 2 deletions lib/licensed/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ def list
run Licensed::Commands::List.new(config: config), sources: options[:sources], reporter: options[:format], licenses: options[:licenses]
end

desc "notices", "Generate a NOTICE file from cached records"
desc "notices", "Generate a NOTICE file with dependency data"
method_option :config, aliases: "-c", type: :string,
desc: "Path to licensed configuration file"
method_option :sources, aliases: "-s", type: :array,
desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
method_option :computed, aliases: "-l", type: :boolean,
desc: "Whether to generate a NOTICE file using computed data or cached records"
Comment on lines +54 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this option be added to the licensed notices command documentation?

Copy link
Contributor Author

@jonabc jonabc Nov 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 yep, I had updated the docs locally but it looks like I never pushed the commit up to GitHub. Thanks for catching that!

def notices
run Licensed::Commands::Notices.new(config: config), sources: options[:sources]
run Licensed::Commands::Notices.new(config: config), sources: options[:sources], computed: options[:computed]
end

map "-v" => :version
Expand Down
31 changes: 27 additions & 4 deletions lib/licensed/commands/notices.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def default_reporter(options)

protected

# Load stored dependency record data to add to the notices report.
# Load a dependency record data and add it to the notices report.
#
# app - The application configuration for the dependency
# source - The dependency source enumerator for the dependency
Expand All @@ -22,13 +22,36 @@ def default_reporter(options)
#
# Returns true.
def evaluate_dependency(app, source, dependency, report)
report["record"] =
if load_dependency_record_from_files
load_cached_dependency_record(app, source, dependency, report)
else
dependency.record
end

true
end

# Loads a dependency record from a cached file.
#
# app - The application configuration for the dependency
# source - The dependency source enumerator for the dependency
# dependency - An application dependency
# report - A report hash for the command to provide extra data for the report output.
#
# Returns a dependency record or nil if one doesn't exist
def load_cached_dependency_record(app, source, dependency, report)
filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
report["cached_record"] = Licensed::DependencyRecord.read(filename)
if !report["cached_record"]
record = Licensed::DependencyRecord.read(filename)
if !record
report.warnings << "expected cached record not found at #{filename}"
end

true
record
end

def load_dependency_record_from_files
!options.fetch(:computed, false)
end
end
end
Expand Down
10 changes: 5 additions & 5 deletions lib/licensed/reporters/notices_reporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ def end_report_dependency(dependency, report)
def notices(report)
return unless report.target.is_a?(Licensed::Dependency)

cached_record = report["cached_record"]
return unless cached_record
record = report["record"]
return unless record

texts = cached_record.licenses.map(&:text)
cached_record.notices.each do |notice|
texts = record.licenses.map(&:text)
record.notices.each do |notice|
case notice
when Hash
texts << notice["text"]
Expand All @@ -70,7 +70,7 @@ def notices(report)
end

<<~NOTICE
#{cached_record["name"]}@#{cached_record["version"]}
#{record["name"]}@#{record["version"]}

#{texts.map(&:strip).reject(&:empty?).compact.join(TEXT_SEPARATOR)}
NOTICE
Expand Down
31 changes: 26 additions & 5 deletions test/commands/notices_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,36 @@ def dependency_report(app, source, dependency_name = "dependency")
source.dependencies.each do |dependency|
report = dependency_report(app, source, dependency.name)
assert report
assert_equal dependency.record["name"], report["cached_record"]["name"]
assert_equal dependency.record["version"], report["cached_record"]["version"]
assert_equal dependency.record.content, report["cached_record"].content
assert_equal dependency.record["name"], report["record"]["name"]
assert_equal dependency.record["version"], report["record"]["version"]
assert_equal dependency.record.content, report["record"].content
end
end
end
end

it "reports a warning on missing cached records" do
it "reports computed records found for dependencies" do
# delete all cached files for dependencies
config.apps.each do |app|
FileUtils.rm_rf app.cache_path
end

run_command(computed: true)

config.apps.each do |app|
app.sources.each do |source|
source.dependencies.each do |dependency|
report = dependency_report(app, source, dependency.name)
assert report
assert_equal dependency.record["name"], report["record"]["name"]
assert_equal dependency.record["version"], report["record"]["version"]
assert_equal dependency.record.content, report["record"].content
end
end
end
end

it "reports a warning on missing records" do
config.apps.each { |app| FileUtils.rm_rf app.cache_path }
run_command

Expand All @@ -57,7 +78,7 @@ def dependency_report(app, source, dependency_name = "dependency")
source.dependencies.each do |dependency|
report = dependency_report(app, source, dependency.name)
assert report
assert_nil report["cached_record"]
assert_nil report["record"]
path = app.cache_path.join(source.class.type, "#{dependency.name}.#{Licensed::DependencyRecord::EXTENSION}")
assert_equal ["expected cached record not found at #{path}"],
report.warnings
Expand Down
6 changes: 3 additions & 3 deletions test/reporters/notices_reporter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
end

it "prints a warning if dependency notice contents can't be parsed" do
dependency_report["cached_record"] = Licensed::DependencyRecord.new(
dependency_report["record"] = Licensed::DependencyRecord.new(
licenses: [],
notices: [1]
)
Expand All @@ -54,7 +54,7 @@
end

it "writes dependencies' licenses and notices to a NOTICE file" do
dependency_report["cached_record"] = Licensed::DependencyRecord.new(
dependency_report["record"] = Licensed::DependencyRecord.new(
licenses: [
{ "sources" => "LICENSE1", "text" => "license1" },
{ "sources" => "LICENSE2", "text" => "license2" }
Expand All @@ -77,7 +77,7 @@

it "writes dependencies' licenses and notices to a NOTICE.<app> file in a shared cache" do
app["shared_cache"] = true
dependency_report["cached_record"] = Licensed::DependencyRecord.new(
dependency_report["record"] = Licensed::DependencyRecord.new(
licenses: [
{ "sources" => "LICENSE1", "text" => "license1" },
{ "sources" => "LICENSE2", "text" => "license2" }
Expand Down