dev-cmd/audit: update license checks to new style

This commit is contained in:
Rylan Polster 2020-08-18 11:00:17 -04:00
parent 60ec30d41e
commit e215b3df75
2 changed files with 260 additions and 69 deletions

View File

@ -119,7 +119,8 @@ module Homebrew
# Check style in a single batch run up front for performance
style_results = Style.check_style_json(style_files, options) if style_files
# load licenses
spdx_data = SPDX.spdx_data
spdx_license_data = SPDX.license_data
spdx_exception_data = SPDX.exception_data
new_formula_problem_lines = []
audit_formulae.sort.each do |f|
only = only_cops ? ["style"] : args.only
@ -130,7 +131,8 @@ module Homebrew
git: git,
only: only,
except: args.except,
spdx_data: spdx_data,
spdx_license_data: spdx_license_data,
spdx_exception_data: spdx_exception_data,
}
options[:style_offenses] = style_results.file_offenses(f.path) if style_results
options[:display_cop_names] = args.display_cop_names?
@ -228,7 +230,8 @@ module Homebrew
@new_formula_problems = []
@text = FormulaText.new(formula.path)
@specs = %w[stable devel head].map { |s| formula.send(s) }.compact
@spdx_data = options[:spdx_data]
@spdx_license_data = options[:spdx_license_data]
@spdx_exception_data = options[:spdx_exception_data]
end
def audit_style
@ -357,43 +360,49 @@ module Homebrew
def audit_license
if formula.license.present?
non_standard_licenses = formula.license.map do |license|
next if license == :public_domain
next if @spdx_data["licenses"].any? { |spdx| spdx["licenseId"] == license }
license
end.compact
licenses, exceptions = SPDX.parse_license_expression formula.license
non_standard_licenses = licenses.reject { |license| SPDX.valid_license? license }
if non_standard_licenses.present?
problem "Formula #{formula.name} contains non-standard SPDX licenses: #{non_standard_licenses}."
problem <<~EOS
Formula #{formula.name} contains non-standard SPDX licenses: #{non_standard_licenses}.
For a list of valid licenses check: #{Formatter.url("https://spdx.org/licenses/")}
EOS
end
if @strict
deprecated_licenses = formula.license.map do |license|
next if license == :public_domain
next if @spdx_data["licenses"].any? do |spdx|
spdx["licenseId"] == license && !spdx["isDeprecatedLicenseId"]
deprecated_licenses = licenses.select do |license|
SPDX.deprecated_license? license
end
license
end.compact
if deprecated_licenses.present?
problem "Formula #{formula.name} contains deprecated SPDX licenses: #{deprecated_licenses}."
problem <<~EOS
Formula #{formula.name} contains deprecated SPDX licenses: #{deprecated_licenses}.
You may need to add `-only` or `-or-later` for GNU licenses (e.g. `GPL`, `LGPL`, `AGPL`, `GFDL`).
For a list of valid licenses check: #{Formatter.url("https://spdx.org/licenses/")}
EOS
end
end
invalid_exceptions = exceptions.reject { |exception| SPDX.valid_license_exception? exception }
if invalid_exceptions.present?
problem <<~EOS
Formula #{formula.name} contains invalid or deprecated SPDX license exceptions: #{invalid_exceptions}.
For a list of valid license exceptions check:
#{Formatter.url("https://spdx.org/licenses/exceptions-index.html/")}
EOS
end
return unless @online
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*})
return if user.blank?
github_license = GitHub.get_repo_license(user, repo)
return if github_license && (formula.license + ["NOASSERTION"]).include?(github_license)
return if PERMITTED_LICENSE_MISMATCHES[github_license]&.any? { |license| formula.license.include? license }
return if github_license && (licenses + ["NOASSERTION"]).include?(github_license)
return if PERMITTED_LICENSE_MISMATCHES[github_license]&.any? { |license| licenses.include? license }
return if PERMITTED_FORMULA_LICENSE_MISMATCHES[formula.name] == formula.version
problem "Formula license #{formula.license} does not match GitHub license #{Array(github_license)}."
problem "Formula license #{licenses} does not match GitHub license #{Array(github_license)}."
elsif @new_formula && @core_tap
problem "Formulae in homebrew/core must specify a license."

View File

@ -3,6 +3,7 @@
require "dev-cmd/audit"
require "formulary"
require "cmd/shared_examples/args_parse"
require "utils/spdx"
describe "Homebrew.audit_args" do
it_behaves_like "parseable arguments"
@ -80,20 +81,21 @@ module Homebrew
end
describe "#audit_license" do
let(:spdx_data) {
JSON.parse Pathname(File.join(File.dirname(__FILE__), "../../data/spdx.json")).read
}
let(:spdx_license_data) { SPDX.license_data }
let(:spdx_exception_data) { SPDX.exception_data }
let(:custom_spdx_id) { "zzz" }
let(:deprecated_spdx_id) { "GPL-1.0" }
let(:standard_mismatch_spdx_id) { "0BSD" }
let(:license_array) { ["0BSD", "GPL-3.0"] }
let(:license_array_mismatch) { ["0BSD", "MIT"] }
let(:license_array_nonstandard) { ["0BSD", "zzz", "MIT"] }
let(:license_array_deprecated) { ["0BSD", "GPL-1.0", "MIT"] }
let(:license_all_custom_id) { 'all_of: ["MIT", "zzz"]' }
let(:deprecated_spdx_exception) { "Nokia-Qt-exception-1.1" }
let(:license_any) { 'any_of: ["0BSD", "GPL-3.0-only"]' }
let(:license_any_with_plus) { 'any_of: ["0BSD+", "GPL-3.0-only"]' }
let(:license_nested_conditions) { 'any_of: ["0BSD", { all_of: ["GPL-3.0-only", "MIT"] }]' }
let(:license_any_mismatch) { 'any_of: ["0BSD", "MIT"]' }
let(:license_any_nonstandard) { 'any_of: ["0BSD", "zzz", "MIT"]' }
let(:license_any_deprecated) { 'any_of: ["0BSD", "GPL-1.0", "MIT"]' }
it "does not check if the formula is not a new formula" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: false
fa = formula_auditor "foo", <<~RUBY, new_formula: false
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
end
@ -104,7 +106,7 @@ module Homebrew
end
it "detects no license info" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true, core_tap: true
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true, core_tap: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
end
@ -115,19 +117,22 @@ module Homebrew
end
it "detects if license is not a standard spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "#{custom_spdx_id}"
license "zzz"
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "Formula foo contains non-standard SPDX licenses: [\"zzz\"]."
expect(fa.problems.first).to match <<~EOS
Formula foo contains non-standard SPDX licenses: ["zzz"].
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "detects if license is a deprecated spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true, strict: true
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true, strict: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "#{deprecated_spdx_id}"
@ -135,35 +140,61 @@ module Homebrew
RUBY
fa.audit_license
expect(fa.problems.first).to match "Formula foo contains deprecated SPDX licenses: [\"GPL-1.0\"]."
expect(fa.problems.first).to match <<~EOS
Formula foo contains deprecated SPDX licenses: ["GPL-1.0"].
You may need to add `-only` or `-or-later` for GNU licenses (e.g. `GPL`, `LGPL`, `AGPL`, `GFDL`).
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "detects if license with AND contains a non-standard spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_all_custom_id}
end
RUBY
fa.audit_license
expect(fa.problems.first).to match <<~EOS
Formula foo contains non-standard SPDX licenses: ["zzz"].
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "detects if license array contains a non-standard spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_array_nonstandard}
license #{license_any_nonstandard}
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "Formula foo contains non-standard SPDX licenses: [\"zzz\"]."
expect(fa.problems.first).to match <<~EOS
Formula foo contains non-standard SPDX licenses: ["zzz"].
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "detects if license array contains a deprecated spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true, strict: true
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true, strict: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_array_deprecated}
license #{license_any_deprecated}
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "Formula foo contains deprecated SPDX licenses: [\"GPL-1.0\"]."
expect(fa.problems.first).to match <<~EOS
Formula foo contains deprecated SPDX licenses: ["GPL-1.0"].
You may need to add `-only` or `-or-later` for GNU licenses (e.g. `GPL`, `LGPL`, `AGPL`, `GFDL`).
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "verifies that a license info is a standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "0BSD"
@ -174,11 +205,85 @@ module Homebrew
expect(fa.problems).to be_empty
end
it "verifies that a license array contains only standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
it "verifies that a license info with plus is a standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_array}
license "0BSD+"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "allows :public_domain license" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license :public_domain
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license info with multiple licenses are standard spdx ids" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license any_of: ["0BSD", "MIT"]
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license info with exceptions are standard spdx ids" do
formula_text = <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "Apache-2.0" => { with: "LLVM-exception" }
end
RUBY
fa = formula_auditor "foo", formula_text, new_formula: true,
spdx_license_data: spdx_license_data, spdx_exception_data: spdx_exception_data
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license array contains only standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_any}
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license array contains only standard spdx id with plus" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_any_with_plus}
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license array with AND contains only standard spdx ids" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data: spdx_license_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_nested_conditions}
end
RUBY
@ -188,21 +293,93 @@ module Homebrew
it "checks online and verifies that a standard license id is the same "\
"as what is indicated on its Github repo" do
fa = formula_auditor "cask", <<~RUBY, spdx_data: spdx_data, online: true, core_tap: true, new_formula: true
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0"
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data: spdx_license_data,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and verifies that a standard license id with AND is the same "\
"as what is indicated on its Github repo" do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license all_of: ["GPL-3.0-or-later", "MIT"]
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data: spdx_license_data,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and verifies that a standard license id with WITH is the same "\
"as what is indicated on its Github repo" do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0-or-later" => { with: "LLVM-exception" }
end
RUBY
fa = formula_auditor "cask", formula_text, online: true, core_tap: true, new_formula: true,
spdx_license_data: spdx_license_data, spdx_exception_data: spdx_exception_data
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license exception has standard spdx ids" do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0-or-later" => { with: "zzz" }
end
RUBY
fa = formula_auditor "cask", formula_text, core_tap: true, new_formula: true,
spdx_license_data: spdx_license_data, spdx_exception_data: spdx_exception_data
fa.audit_license
expect(fa.problems.first).to match <<~EOS
Formula cask contains invalid or deprecated SPDX license exceptions: ["zzz"].
For a list of valid license exceptions check:
https://spdx.org/licenses/exceptions-index.html/
EOS
end
it "verifies that a license exception has non-deprecated spdx ids" do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0-or-later" => { with: "#{deprecated_spdx_exception}" }
end
RUBY
fa = formula_auditor "cask", formula_text, core_tap: true, new_formula: true,
spdx_license_data: spdx_license_data, spdx_exception_data: spdx_exception_data
fa.audit_license
expect(fa.problems.first).to match <<~EOS
Formula cask contains invalid or deprecated SPDX license exceptions: ["#{deprecated_spdx_exception}"].
For a list of valid license exceptions check:
https://spdx.org/licenses/exceptions-index.html/
EOS
end
it "checks online and verifies that a standard license id is in the same exempted license group" \
"as what is indicated on its GitHub repo" do
fa = formula_auditor "cask", <<~RUBY, spdx_data: spdx_data, online: true, new_formula: true
fa = formula_auditor "cask", <<~RUBY, spdx_license_data: spdx_license_data, online: true, new_formula: true
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
@ -216,11 +393,11 @@ module Homebrew
it "checks online and verifies that a standard license array is in the same exempted license group" \
"as what is indicated on its GitHub repo" do
fa = formula_auditor "cask", <<~RUBY, spdx_data: spdx_data, online: true, new_formula: true
fa = formula_auditor "cask", <<~RUBY, spdx_license_data: spdx_license_data, online: true, new_formula: true
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license ["GPL-3.0-or-later", "MIT"]
license any_of: ["GPL-3.0-or-later", "MIT"]
end
RUBY
@ -230,43 +407,48 @@ module Homebrew
it "checks online and detects that a formula-specified license is not "\
"the same as what is indicated on its Github repository" do
fa = formula_auditor "cask", <<~RUBY, online: true, spdx_data: spdx_data, core_tap: true, new_formula: true
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "#{standard_mismatch_spdx_id}"
license "0BSD"
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data: spdx_license_data,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems.first).to match "Formula license #{Array(standard_mismatch_spdx_id)} "\
"does not match GitHub license [\"GPL-3.0\"]."
expect(fa.problems.first).to match "Formula license [\"0BSD\"] does not match GitHub license [\"GPL-3.0\"]."
end
it "checks online and detects that an array of license does not contain "\
"what is indicated on its Github repository" do
fa = formula_auditor "cask", <<~RUBY, online: true, spdx_data: spdx_data, core_tap: true, new_formula: true
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license #{license_array_mismatch}
license #{license_any_mismatch}
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data: spdx_license_data,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems.first).to match "Formula license #{license_array_mismatch} "\
expect(fa.problems.first).to match "Formula license [\"0BSD\", \"MIT\"] "\
"does not match GitHub license [\"GPL-3.0\"]."
end
it "checks online and verifies that an array of license contains "\
"what is indicated on its Github repository" do
fa = formula_auditor "cask", <<~RUBY, online: true, spdx_data: spdx_data, core_tap: true, new_formula: true
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license #{license_array}
license #{license_any}
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data: spdx_license_data,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems).to be_empty