commit
8c8f2df85f
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
@ -114,6 +114,9 @@ jobs:
|
||||
- name: Run brew man
|
||||
run: brew man --fail-if-changed
|
||||
|
||||
- name: Check for outdated license data
|
||||
run: brew update-license-data --fail-if-changed
|
||||
|
||||
- name: Run brew tests
|
||||
run: |
|
||||
# brew tests doesn't like world writable directories
|
||||
|
@ -211,6 +211,8 @@ module Homebrew
|
||||
|
||||
puts "From: #{Formatter.url(github_info(f))}"
|
||||
|
||||
puts "License: #{f.license}" if f.license
|
||||
|
||||
unless f.deps.empty?
|
||||
ohai "Dependencies"
|
||||
%w[build required recommended optional].map do |type|
|
||||
|
5298
Library/Homebrew/data/spdx.json
Normal file
5298
Library/Homebrew/data/spdx.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@ require "date"
|
||||
require "missing_formula"
|
||||
require "digest"
|
||||
require "cli/parser"
|
||||
require "json"
|
||||
|
||||
module Homebrew
|
||||
module_function
|
||||
@ -109,7 +110,9 @@ 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 = HOMEBREW_LIBRARY_PATH/"data/spdx.json"
|
||||
spdx_data = JSON.parse(spdx.read)
|
||||
new_formula_problem_lines = []
|
||||
audit_formulae.sort.each do |f|
|
||||
only = only_cops ? ["style"] : args.only
|
||||
@ -120,6 +123,7 @@ module Homebrew
|
||||
git: git,
|
||||
only: only,
|
||||
except: args.except,
|
||||
spdx_data: spdx_data,
|
||||
}
|
||||
options[:style_offenses] = style_results.file_offenses(f.path) if style_results
|
||||
options[:display_cop_names] = args.display_cop_names?
|
||||
@ -215,6 +219,7 @@ 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]
|
||||
end
|
||||
|
||||
def audit_style
|
||||
@ -327,6 +332,27 @@ module Homebrew
|
||||
openssl@1.1
|
||||
].freeze
|
||||
|
||||
def audit_license
|
||||
if formula.license.present?
|
||||
if @spdx_data["licenses"].any? { |lic| lic["licenseId"] == formula.license }
|
||||
return unless @online
|
||||
|
||||
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
|
||||
return if user.blank?
|
||||
|
||||
github_license = GitHub.get_repo_license(user, repo)
|
||||
return if github_license && (github_license == formula.license)
|
||||
|
||||
problem "License mismatch - GitHub license is: #{github_license}, "\
|
||||
"but Formulae license states: #{formula.license}."
|
||||
else
|
||||
problem "#{formula.license} is not a standard SPDX license."
|
||||
end
|
||||
elsif @new_formula
|
||||
problem "No license specified for package."
|
||||
end
|
||||
end
|
||||
|
||||
def audit_deps
|
||||
@specs.each do |spec|
|
||||
# Check for things we don't like to depend on.
|
||||
@ -502,8 +528,9 @@ module Homebrew
|
||||
end
|
||||
|
||||
def audit_github_repository
|
||||
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*})
|
||||
return if user.nil?
|
||||
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
|
||||
|
||||
return if user.blank?
|
||||
|
||||
warning = SharedAudits.github(user, repo)
|
||||
return if warning.nil?
|
||||
@ -512,8 +539,8 @@ module Homebrew
|
||||
end
|
||||
|
||||
def audit_gitlab_repository
|
||||
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*})
|
||||
return if user.nil?
|
||||
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
|
||||
return if user.blank?
|
||||
|
||||
warning = SharedAudits.gitlab(user, repo)
|
||||
return if warning.nil?
|
||||
@ -522,8 +549,8 @@ module Homebrew
|
||||
end
|
||||
|
||||
def audit_bitbucket_repository
|
||||
user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*})
|
||||
return if user.nil?
|
||||
user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*}) if @new_formula
|
||||
return if user.blank?
|
||||
|
||||
warning = SharedAudits.bitbucket(user, repo)
|
||||
return if warning.nil?
|
||||
@ -534,7 +561,6 @@ module Homebrew
|
||||
def get_repo_data(regex)
|
||||
return unless @core_tap
|
||||
return unless @online
|
||||
return unless @new_formula
|
||||
|
||||
_, user, repo = *regex.match(formula.stable.url) if formula.stable
|
||||
_, user, repo = *regex.match(formula.homepage) unless user
|
||||
|
@ -47,6 +47,8 @@ module Homebrew
|
||||
description: "Explicitly set the <name> of the new formula."
|
||||
flag "--set-version=",
|
||||
description: "Explicitly set the <version> of the new formula."
|
||||
flag "--set-license=",
|
||||
description: "Explicitly set the <license> of the new formula."
|
||||
flag "--tap=",
|
||||
description: "Generate the new formula within the given tap, specified as <user>`/`<repo>."
|
||||
switch :force
|
||||
@ -68,11 +70,13 @@ module Homebrew
|
||||
|
||||
version = args.set_version
|
||||
name = args.set_name
|
||||
license = args.set_license
|
||||
tap = args.tap
|
||||
|
||||
fc = FormulaCreator.new
|
||||
fc.name = name
|
||||
fc.version = version
|
||||
fc.license = license
|
||||
fc.tap = Tap.fetch(tap || "homebrew/core")
|
||||
raise TapUnavailableError, tap unless fc.tap.installed?
|
||||
|
||||
|
39
Library/Homebrew/dev-cmd/update-license-data.rb
Normal file
39
Library/Homebrew/dev-cmd/update-license-data.rb
Normal file
@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "commands"
|
||||
require "cli/parser"
|
||||
require "json"
|
||||
require "net/http"
|
||||
require "open-uri"
|
||||
|
||||
module Homebrew
|
||||
module_function
|
||||
|
||||
SPDX_PATH = (HOMEBREW_LIBRARY_PATH/"data/spdx.json").freeze
|
||||
SPDX_DATA_URL = "https://raw.githubusercontent.com/spdx/license-list-data/HEAD/json/licenses.json"
|
||||
|
||||
def update_license_data_args
|
||||
Homebrew::CLI::Parser.new do
|
||||
usage_banner <<~EOS
|
||||
`update_license_data` <cmd>
|
||||
|
||||
Update SPDX license data in the Homebrew repository.
|
||||
EOS
|
||||
switch "--fail-if-changed",
|
||||
description: "Return a failing status code if current license data's version is different from " \
|
||||
"the upstream. This can be used to notify CI when the SPDX license data is out of date."
|
||||
|
||||
max_named 0
|
||||
end
|
||||
end
|
||||
|
||||
def update_license_data
|
||||
update_license_data_args.parse
|
||||
ohai "Updating SPDX license data..."
|
||||
curl_download(SPDX_DATA_URL, to: SPDX_PATH)
|
||||
|
||||
return unless args.fail_if_changed?
|
||||
|
||||
system("git diff --stat --exit-code #{SPDX_PATH}")
|
||||
end
|
||||
end
|
@ -351,6 +351,9 @@ class Formula
|
||||
# @see .desc=
|
||||
delegate desc: :"self.class"
|
||||
|
||||
# The SPDX ID of the software license.
|
||||
delegate license: :"self.class"
|
||||
|
||||
# The homepage for the software.
|
||||
# @method homepage
|
||||
# @see .homepage=
|
||||
@ -1687,6 +1690,7 @@ class Formula
|
||||
"aliases" => aliases.sort,
|
||||
"versioned_formulae" => versioned_formulae.map(&:name),
|
||||
"desc" => desc,
|
||||
"license" => license,
|
||||
"homepage" => homepage,
|
||||
"versions" => {
|
||||
"stable" => stable&.version&.to_s,
|
||||
@ -2211,6 +2215,13 @@ class Formula
|
||||
# <pre>desc "Example formula"</pre>
|
||||
attr_rw :desc
|
||||
|
||||
# @!attribute [w]
|
||||
# The SPDX ID of the open-source license that the formula uses.
|
||||
# Shows when running `brew info`.
|
||||
#
|
||||
# <pre>license "BSD-2-Clause"</pre>
|
||||
attr_rw :license
|
||||
|
||||
# @!attribute [w] homepage
|
||||
# The homepage for the software. Used by users to get more information
|
||||
# about the software and Homebrew maintainers as a point of contact for
|
||||
|
@ -6,7 +6,7 @@ require "erb"
|
||||
module Homebrew
|
||||
class FormulaCreator
|
||||
attr_reader :url, :sha256, :desc, :homepage
|
||||
attr_accessor :name, :version, :tap, :path, :mode
|
||||
attr_accessor :name, :version, :tap, :path, :mode, :license
|
||||
|
||||
def url=(url)
|
||||
@url = url
|
||||
@ -100,6 +100,7 @@ module Homebrew
|
||||
<% end %>
|
||||
sha256 "#{sha256}"
|
||||
<% end %>
|
||||
license "#{license}"
|
||||
|
||||
<% if mode == :cmake %>
|
||||
depends_on "cmake" => :build
|
||||
|
@ -24,7 +24,8 @@ module RuboCop
|
||||
[{ name: :mirror, type: :method_call }],
|
||||
[{ name: :version, type: :method_call }],
|
||||
[{ name: :sha256, type: :method_call }],
|
||||
[{ name: :revision, type: :method_call }],
|
||||
[{ name: :license, type: :method_call }],
|
||||
[{ name: :revision, type: :method_call }],
|
||||
[{ name: :version_scheme, type: :method_call }],
|
||||
[{ name: :head, type: :method_call }],
|
||||
[{ name: :stable, type: :block_call }],
|
||||
|
@ -79,6 +79,92 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
describe "#audit_license" do
|
||||
let(:spdx_data) {
|
||||
JSON.parse Pathname(File.join(File.dirname(__FILE__), "../../data/spdx.json")).read
|
||||
}
|
||||
|
||||
let(:custom_spdx_id) { "zzz" }
|
||||
let(:standard_mismatch_spdx_id) { "0BSD" }
|
||||
|
||||
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
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
license ""
|
||||
end
|
||||
RUBY
|
||||
|
||||
fa.audit_license
|
||||
expect(fa.problems).to be_empty
|
||||
end
|
||||
|
||||
it "detects no license info" do
|
||||
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
license ""
|
||||
end
|
||||
RUBY
|
||||
|
||||
fa.audit_license
|
||||
expect(fa.problems.first).to match "No license specified for package."
|
||||
end
|
||||
|
||||
it "detects if license is not a standard spdx-id" do
|
||||
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
license "#{custom_spdx_id}"
|
||||
end
|
||||
RUBY
|
||||
|
||||
fa.audit_license
|
||||
expect(fa.problems.first).to match "#{custom_spdx_id} is not a standard SPDX license."
|
||||
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
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
license "0BSD"
|
||||
end
|
||||
RUBY
|
||||
|
||||
fa.audit_license
|
||||
expect(fa.problems).to be_empty
|
||||
end
|
||||
|
||||
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
|
||||
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.audit_license
|
||||
expect(fa.problems).to be_empty
|
||||
end
|
||||
|
||||
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
|
||||
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}"
|
||||
end
|
||||
RUBY
|
||||
|
||||
fa.audit_license
|
||||
expect(fa.problems.first).to match "License mismatch - GitHub license is: GPL-3.0, "\
|
||||
"but Formulae license states: #{standard_mismatch_spdx_id}."
|
||||
end
|
||||
end
|
||||
|
||||
describe "#audit_file" do
|
||||
specify "no issue" do
|
||||
fa = formula_auditor "foo", <<~RUBY
|
||||
|
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cmd/shared_examples/args_parse"
|
||||
|
||||
describe "Homebrew.update_license_data_args" do
|
||||
it_behaves_like "parseable arguments"
|
||||
end
|
@ -19,6 +19,18 @@ describe RuboCop::Cop::FormulaAudit::ComponentsOrder do
|
||||
RUBY
|
||||
end
|
||||
|
||||
it "When license precedes sha256" do
|
||||
expect_offense(<<~RUBY)
|
||||
class Foo < Formula
|
||||
homepage "https://brew.sh"
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
license "0BSD"
|
||||
sha256 "samplesha256"
|
||||
^^^^^^^^^^^^^^^^^^^^^ `sha256` (line 5) should be put before `license` (line 4)
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it "When `bottle` precedes `livecheck`" do
|
||||
expect_offense(<<~RUBY)
|
||||
class Foo < Formula
|
||||
|
@ -474,6 +474,15 @@ module GitHub
|
||||
open_api(url, scopes: ["admin:org", "user"], data: data, request_method: "POST")
|
||||
end
|
||||
|
||||
def get_repo_license(user, repo)
|
||||
response = GitHub.open_api("#{GitHub::API_URL}/repos/#{user}/#{repo}/license")
|
||||
return unless response.key?("license")
|
||||
|
||||
response["license"]["spdx_id"]
|
||||
rescue GitHub::HTTPNotFoundError
|
||||
nil
|
||||
end
|
||||
|
||||
def api_errors
|
||||
[GitHub::AuthenticationFailedError, GitHub::HTTPNotFoundError,
|
||||
GitHub::RateLimitExceededError, GitHub::Error, JSON::ParserError].freeze
|
||||
|
@ -86,6 +86,7 @@ unpin
|
||||
untap
|
||||
up
|
||||
update
|
||||
update-license-data
|
||||
update-report
|
||||
update-reset
|
||||
update-test
|
||||
|
@ -793,6 +793,8 @@ a simple example. For the complete API, see:
|
||||
Explicitly set the *`name`* of the new formula.
|
||||
* `--set-version`:
|
||||
Explicitly set the *`version`* of the new formula.
|
||||
* `--set-license`:
|
||||
Explicitly set the *`license`* of the new formula.
|
||||
* `--tap`:
|
||||
Generate the new formula within the given tap, specified as *`user`*`/`*`repo`*.
|
||||
|
||||
@ -1028,6 +1030,13 @@ directory.
|
||||
* `-g`, `--git`:
|
||||
Initialise a Git repository in the unpacked source. This is useful for creating patches for the software.
|
||||
|
||||
### `update_license_data` *`cmd`*
|
||||
|
||||
Update SPDX license data in the Homebrew repository.
|
||||
|
||||
* `--fail-if-changed`:
|
||||
Return a failing status code if current license data's version is different from the upstream. This can be used to notify CI when the SPDX license data is out of date.
|
||||
|
||||
### `update-test` [*`options`*]
|
||||
|
||||
Run a test of `brew update` with a new repository clone. If no options are
|
||||
|
@ -1047,6 +1047,10 @@ Explicitly set the \fIname\fR of the new formula\.
|
||||
Explicitly set the \fIversion\fR of the new formula\.
|
||||
.
|
||||
.TP
|
||||
\fB\-\-set\-license\fR
|
||||
Explicitly set the \fIlicense\fR of the new formula\.
|
||||
.
|
||||
.TP
|
||||
\fB\-\-tap\fR
|
||||
Generate the new formula within the given tap, specified as \fIuser\fR\fB/\fR\fIrepo\fR\.
|
||||
.
|
||||
@ -1331,6 +1335,13 @@ Patches for \fIformula\fR will be applied to the unpacked source\.
|
||||
\fB\-g\fR, \fB\-\-git\fR
|
||||
Initialise a Git repository in the unpacked source\. This is useful for creating patches for the software\.
|
||||
.
|
||||
.SS "\fBupdate_license_data\fR \fIcmd\fR"
|
||||
Update SPDX license data in the Homebrew repository\.
|
||||
.
|
||||
.TP
|
||||
\fB\-\-fail\-if\-changed\fR
|
||||
Return a failing status code if current license data\'s version is different from the upstream\. This can be used to notify CI when the SPDX license data is out of date\.
|
||||
.
|
||||
.SS "\fBupdate\-test\fR [\fIoptions\fR]"
|
||||
Run a test of \fBbrew update\fR with a new repository clone\. If no options are passed, use \fBorigin/master\fR as the start commit\.
|
||||
.
|
||||
|
Loading…
x
Reference in New Issue
Block a user