Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
37 changes: 19 additions & 18 deletions Library/Homebrew/dev-cmd/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,61 +183,62 @@ def create_formula
:zig
end

fc = FormulaCreator.new(
args.set_name,
args.set_version,
tap: args.tap,
formula_creator = FormulaCreator.new(
url: args.named.fetch(0),
name: args.set_name,
version: args.set_version,
tap: args.tap,
mode:,
license: args.set_license,
fetch: !args.no_fetch?,
head: args.HEAD?,
)
fc.parse_url

# ask for confirmation if name wasn't passed explicitly
if args.set_name.blank?
print "Formula name [#{fc.name}]: "
fc.name = __gets || fc.name
print "Formula name [#{formula_creator.name}]: "
confirmed_name = __gets.presence
formula_creator.name = confirmed_name if confirmed_name.present?
Comment thread
MikeMcQuaid marked this conversation as resolved.
Outdated
end

fc.verify
formula_creator.verify_tap_available!

# Check for disallowed formula, or names that shadow aliases,
# unless --force is specified.
unless args.force?
if (reason = MissingFormula.disallowed_reason(fc.name))
if (reason = MissingFormula.disallowed_reason(formula_creator.name))
odie <<~EOS
The formula '#{fc.name}' is not allowed to be created.
The formula '#{formula_creator.name}' is not allowed to be created.
#{reason}
If you really want to create this formula use `--force`.
EOS
end

Homebrew.with_no_api_env do
if Formula.aliases.include? fc.name
realname = Formulary.canonical_name(fc.name)
if Formula.aliases.include?(formula_creator.name)
realname = Formulary.canonical_name(formula_creator.name)
odie <<~EOS
The formula '#{realname}' is already aliased to '#{fc.name}'.
The formula '#{realname}' is already aliased to '#{formula_creator.name}'.
Please check that you are not creating a duplicate.
To force creation use `--force`.
EOS
end
end
end

path = fc.write_formula!
path = formula_creator.write_formula!

formula = Homebrew.with_no_api_env do
CoreTap.instance.clear_cache
Formula[fc.name]
Formula[formula_creator.name]
end
PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python?

puts <<~EOS
Please audit and test formula before submitting:
HOMEBREW_NO_INSTALL_FROM_API=1 brew audit --new #{fc.name}
HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source --verbose --debug #{fc.name}
HOMEBREW_NO_INSTALL_FROM_API=1 brew test #{fc.name}
HOMEBREW_NO_INSTALL_FROM_API=1 brew audit --new #{formula_creator.name}
HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source --verbose --debug #{formula_creator.name}
HOMEBREW_NO_INSTALL_FROM_API=1 brew test #{formula_creator.name}
EOS
path
end
Expand Down
103 changes: 62 additions & 41 deletions Library/Homebrew/formula_creator.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true

require "digest"
Expand All @@ -7,62 +7,81 @@
module Homebrew
# Class for generating a formula from a template.
Comment thread
abitrolly marked this conversation as resolved.
class FormulaCreator
sig { returns(String) }
attr_accessor :name

sig { returns(Version) }
attr_reader :version

sig { returns(T::Boolean) }
attr_reader :head

sig {
params(name: T.nilable(String), version: T.nilable(String), tap: T.nilable(String), url: String,
params(url: String, name: T.nilable(String), version: T.nilable(String), tap: T.nilable(String),
mode: T.nilable(Symbol), license: T.nilable(String), fetch: T::Boolean, head: T::Boolean).void
}
def initialize(name, version, tap:, url:, mode:, license:, fetch:, head:)
@name = name
@version = Version.new(version) if version
@tap = Tap.fetch(tap || "homebrew/core")
def initialize(url:, name: nil, version: nil, tap: nil, mode: nil, license: nil, fetch: false, head: false)
Comment thread
abitrolly marked this conversation as resolved.
@url = url
@mode = mode
@license = license
@fetch = fetch
@head = head
end

sig { void }
def verify
raise TapUnavailableError, @tap.name unless @tap.installed?
end
if name.blank?
stem = Pathname.new(url).stem
name = if stem.start_with?("index.cgi") && stem.include?("=")
# special cases first
# gitweb URLs e.g. http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary
stem.rpartition("=").last
elsif url =~ %r{github\.com/\S+/(\S+)/(archive|releases)/}
# e.g. https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz
T.must(Regexp.last_match(1))
else
# e.g. http://digit-labs.org/files/tools/synscan/releases/synscan-5.02.tar.gz
pathver = Version.parse(stem).to_s
stem.sub(/[-_.]?#{Regexp.escape(pathver)}$/, "")
end
odebug "name from url: #{name}"
end
@name = T.let(name, String)

sig { params(url: String).returns(T.nilable(String)) }
def self.name_from_url(url)
stem = Pathname.new(url).stem
# special cases first
if stem.start_with? "index.cgi"
# gitweb URLs e.g. http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary
stem.rpartition("=").last
elsif url =~ %r{github\.com/\S+/(\S+)/(archive|releases)/}
# e.g. https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz
Regexp.last_match(1)
version = if version.present?
Version.new(version)
else
# e.g. http://digit-labs.org/files/tools/synscan/releases/synscan-5.02.tar.gz
pathver = Version.parse(stem).to_s
stem.sub(/[-_.]?#{Regexp.escape(pathver)}$/, "")
Version.detect(url)
end
end
@version = T.let(version, Version)

sig { void }
def parse_url
@name = FormulaCreator.name_from_url(@url) if @name.blank?
odebug "name_from_url: #{@name}"
@version = Version.detect(@url) if @version.nil?
tap = if tap.blank?
CoreTap.instance
else
Tap.fetch(tap)
end
@tap = T.let(tap, Tap)

case @url
@mode = T.let(mode.presence, T.nilable(Symbol))
@license = T.let(license.presence, T.nilable(String))
@fetch = fetch

case url
when %r{github\.com/(\S+)/(\S+)\.git}
@head = true
head = true
user = Regexp.last_match(1)
repo = Regexp.last_match(2)
@github = GitHub.repository(user, repo) if @fetch
repository = Regexp.last_match(2)
github = GitHub.repository(user, repository) if fetch
when %r{github\.com/(\S+)/(\S+)/(archive|releases)/}
user = Regexp.last_match(1)
repo = Regexp.last_match(2)
@github = GitHub.repository(user, repo) if @fetch
repository = Regexp.last_match(2)
github = GitHub.repository(user, repository) if fetch
end
@head = head
@github = T.let(github, T.untyped)
Comment thread
abitrolly marked this conversation as resolved.

@sha256 = T.let(nil, T.nilable(String))
@desc = T.let(nil, T.nilable(String))
@homepage = T.let(nil, T.nilable(String))
@license = T.let(nil, T.nilable(String))
end

sig { void }
def verify_tap_available!
raise TapUnavailableError, @tap.name unless @tap.installed?
end

sig { returns(Pathname) }
Expand Down Expand Up @@ -91,7 +110,7 @@ def write_formula!
raise "Downloaded URL is not archive"
end

@sha256 = filepath.sha256
@sha256 = T.let(filepath.sha256, T.nilable(String))
end

if @github
Expand All @@ -106,6 +125,8 @@ def write_formula!
path
end

private

sig { params(name: String).returns(String) }
def latest_versioned_formula(name)
name_prefix = "#{name}@"
Expand Down
61 changes: 39 additions & 22 deletions Library/Homebrew/test/formula_creator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,45 @@
require "formula_creator"

RSpec.describe Homebrew::FormulaCreator do
it "gets name from GitHub archive URL" do
t = described_class.name_from_url("https://github.com/abitrolly/lapce/archive/v0.3.0.tar.gz")
expect(t).to eq("lapce")
end

it "gets name from gitweb URL" do
t = described_class.name_from_url("http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary")
expect(t).to eq("libzipper")
end

it "gets name from GitHub repo URL" do
t = described_class.name_from_url("https://github.com/abitrolly/lapce.git")
expect(t).to eq("lapce")
end

it "gets name from GitHub download URL" do
t = described_class.name_from_url("https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz")
expect(t).to eq("stella")
end
describe ".new" do
tests = {
"generic tarball URL": {
url: "http://digit-labs.org/files/tools/synscan/releases/synscan-5.02.tar.gz",
name: "synscan",
version: "5.02",
Comment thread
MikeMcQuaid marked this conversation as resolved.
},
"gitweb URL": {
url: "http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary",
name: "libzipper",
},
"GitHub repo URL": {
url: "https://github.com/abitrolly/lapce.git",
name: "lapce",
head: true,
},
"GitHub archive URL": {
url: "https://github.com/abitrolly/lapce/archive/v0.3.0.tar.gz",
name: "lapce",
version: "0.3.0",
},
"GitHub download URL": {
url: "https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz",
name: "stella",
version: "6.7",
},
}

it "gets name from generic tarball URL" do
t = described_class.name_from_url("http://digit-labs.org/files/tools/synscan/releases/synscan-5.02.tar.gz")
expect(t).to eq("synscan")
tests.each do |description, test|
it "parses #{description}" do
formula_creator = described_class.new(url: test.fetch(:url))
expect(formula_creator.name).to eq(test.fetch(:name))
if (version = test[:version])
expect(formula_creator.version).to eq(version)
else
expect(formula_creator.version).to be_null
end
expect(formula_creator.head).to eq(test.fetch(:head, false))
Comment thread
abitrolly marked this conversation as resolved.
end
end
end
end