My common mistake is to specify release URL, like
brew crate https://github.com/hugelgupf/p9/releases/tag/v0.3.0
which gives unpacking errors later. It should be archive instead
brew create https://github.com/hugelgupf/p9/archive/refs/tags/v0.3.0.tar.gz
Ideally we can try to autodetect the archive from release page,
but erroring out if downloaded file is HTML page should be handy
for early spotting other URL mistakes too.
253 lines
9.0 KiB
Ruby
253 lines
9.0 KiB
Ruby
# typed: true # rubocop:todo Sorbet/StrictSigil
|
|
# frozen_string_literal: true
|
|
|
|
require "digest"
|
|
require "erb"
|
|
|
|
module Homebrew
|
|
# Class for generating a formula from a template.
|
|
class FormulaCreator
|
|
attr_accessor :name
|
|
|
|
sig {
|
|
params(name: T.nilable(String), version: T.nilable(String), tap: T.nilable(String), url: 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")
|
|
@url = url
|
|
@mode = mode
|
|
@license = license
|
|
@fetch = fetch
|
|
@head = head
|
|
end
|
|
|
|
sig { void }
|
|
def verify
|
|
raise TapUnavailableError, @tap.name unless @tap.installed?
|
|
end
|
|
|
|
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)
|
|
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
|
|
end
|
|
|
|
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?
|
|
|
|
case @url
|
|
when %r{github\.com/(\S+)/(\S+)\.git}
|
|
@head = true
|
|
user = Regexp.last_match(1)
|
|
repo = Regexp.last_match(2)
|
|
@github = GitHub.repository(user, repo) 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
|
|
end
|
|
end
|
|
|
|
sig { returns(Pathname) }
|
|
def write_formula!
|
|
raise ArgumentError, "name is blank!" if @name.blank?
|
|
raise ArgumentError, "tap is blank!" if @tap.blank?
|
|
|
|
path = @tap.new_formula_path(@name)
|
|
raise "#{path} already exists" if path.exist?
|
|
|
|
if @version.nil? || @version.null?
|
|
odie "Version cannot be determined from URL. Explicitly set the version with `--set-version` instead."
|
|
end
|
|
|
|
if @fetch
|
|
unless @head
|
|
r = Resource.new
|
|
r.url(@url)
|
|
r.owner = self
|
|
filepath = r.fetch
|
|
raise "Downloaded URL is not archive" if File.read(filepath, 100).strip.start_with?("<!DOCTYPE html>")
|
|
|
|
@sha256 = filepath.sha256
|
|
end
|
|
|
|
if @github
|
|
@desc = @github["description"]
|
|
@homepage = @github["homepage"]
|
|
@license = @github["license"]["spdx_id"] if @github["license"]
|
|
end
|
|
end
|
|
|
|
path.dirname.mkpath
|
|
path.write ERB.new(template, trim_mode: ">").result(binding)
|
|
path
|
|
end
|
|
|
|
sig { params(name: String).returns(String) }
|
|
def latest_versioned_formula(name)
|
|
name_prefix = "#{name}@"
|
|
CoreTap.instance.formula_names
|
|
.select { |f| f.start_with?(name_prefix) }
|
|
.max_by { |v| Gem::Version.new(v.sub(name_prefix, "")) } || "python"
|
|
end
|
|
|
|
sig { returns(String) }
|
|
def template
|
|
# FIXME: https://github.com/errata-ai/vale/issues/818
|
|
# <!-- vale off -->
|
|
<<~ERB
|
|
# Documentation: https://docs.brew.sh/Formula-Cookbook
|
|
# https://rubydoc.brew.sh/Formula
|
|
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
|
|
class #{Formulary.class_s(name)} < Formula
|
|
<% if @mode == :python %>
|
|
include Language::Python::Virtualenv
|
|
|
|
<% end %>
|
|
desc "#{@desc}"
|
|
homepage "#{@homepage}"
|
|
<% unless @head %>
|
|
url "#{@url}"
|
|
<% unless @version.detected_from_url? %>
|
|
version "#{@version}"
|
|
<% end %>
|
|
sha256 "#{@sha256}"
|
|
<% end %>
|
|
license "#{@license}"
|
|
<% if @head %>
|
|
head "#{@url}"
|
|
<% end %>
|
|
|
|
<% if @mode == :cmake %>
|
|
depends_on "cmake" => :build
|
|
<% elsif @mode == :crystal %>
|
|
depends_on "crystal" => :build
|
|
<% elsif @mode == :go %>
|
|
depends_on "go" => :build
|
|
<% elsif @mode == :meson %>
|
|
depends_on "meson" => :build
|
|
depends_on "ninja" => :build
|
|
<% elsif @mode == :node %>
|
|
depends_on "node"
|
|
<% elsif @mode == :perl %>
|
|
uses_from_macos "perl"
|
|
<% elsif @mode == :python %>
|
|
depends_on "#{latest_versioned_formula("python")}"
|
|
<% elsif @mode == :ruby %>
|
|
uses_from_macos "ruby"
|
|
<% elsif @mode == :rust %>
|
|
depends_on "rust" => :build
|
|
<% elsif @mode == :zig %>
|
|
depends_on "zig" => :build
|
|
<% elsif @mode.nil? %>
|
|
# depends_on "cmake" => :build
|
|
<% end %>
|
|
|
|
<% if @mode == :perl || :python || :ruby %>
|
|
# Additional dependency
|
|
# resource "" do
|
|
# url ""
|
|
# sha256 ""
|
|
# end
|
|
|
|
<% end %>
|
|
def install
|
|
<% if @mode == :cmake %>
|
|
system "cmake", "-S", ".", "-B", "build", *std_cmake_args
|
|
system "cmake", "--build", "build"
|
|
system "cmake", "--install", "build"
|
|
<% elsif @mode == :autotools %>
|
|
# Remove unrecognized options if they cause configure to fail
|
|
# https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method
|
|
system "./configure", "--disable-silent-rules", *std_configure_args
|
|
system "make", "install" # if this fails, try separate make/make install steps
|
|
<% elsif @mode == :crystal %>
|
|
system "shards", "build", "--release"
|
|
bin.install "bin/#{name}"
|
|
<% elsif @mode == :go %>
|
|
system "go", "build", *std_go_args(ldflags: "-s -w")
|
|
<% elsif @mode == :meson %>
|
|
system "meson", "setup", "build", *std_meson_args
|
|
system "meson", "compile", "-C", "build", "--verbose"
|
|
system "meson", "install", "-C", "build"
|
|
<% elsif @mode == :node %>
|
|
system "npm", "install", *std_npm_args
|
|
bin.install_symlink Dir["\#{libexec}/bin/*"]
|
|
<% elsif @mode == :perl %>
|
|
ENV.prepend_create_path "PERL5LIB", libexec/"lib/perl5"
|
|
ENV.prepend_path "PERL5LIB", libexec/"lib"
|
|
|
|
# Stage additional dependency (`Makefile.PL` style).
|
|
# resource("").stage do
|
|
# system "perl", "Makefile.PL", "INSTALL_BASE=\#{libexec}"
|
|
# system "make"
|
|
# system "make", "install"
|
|
# end
|
|
|
|
# Stage additional dependency (`Build.PL` style).
|
|
# resource("").stage do
|
|
# system "perl", "Build.PL", "--install_base", libexec
|
|
# system "./Build"
|
|
# system "./Build", "install"
|
|
# end
|
|
|
|
bin.install name
|
|
bin.env_script_all_files(libexec/"bin", PERL5LIB: ENV["PERL5LIB"])
|
|
<% elsif @mode == :python %>
|
|
virtualenv_install_with_resources
|
|
<% elsif @mode == :ruby %>
|
|
ENV["GEM_HOME"] = libexec
|
|
|
|
system "bundle", "install", "-without", "development", "test"
|
|
system "gem", "build", "\#{name}.gemspec"
|
|
system "gem", "install", "\#{name}-\#{version}.gem"
|
|
bin.install libexec/"bin/\#{name}"
|
|
bin.env_script_all_files(libexec/"bin", GEM_HOME: ENV["GEM_HOME"])
|
|
<% elsif @mode == :rust %>
|
|
system "cargo", "install", *std_cargo_args
|
|
<% elsif @mode == :zig %>
|
|
system "zig", "build", *std_zig_args
|
|
<% else %>
|
|
# Remove unrecognized options if they cause configure to fail
|
|
# https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method
|
|
system "./configure", "--disable-silent-rules", *std_configure_args
|
|
# system "cmake", "-S", ".", "-B", "build", *std_cmake_args
|
|
<% end %>
|
|
end
|
|
|
|
test do
|
|
# `test do` will create, run in and delete a temporary directory.
|
|
#
|
|
# This test will fail and we won't accept that! For Homebrew/homebrew-core
|
|
# this will need to be a test that verifies the functionality of the
|
|
# software. Run the test with `brew test #{name}`. Options passed
|
|
# to `brew install` such as `--HEAD` also need to be provided to `brew test`.
|
|
#
|
|
# The installed folder is not in the path, so use the entire path to any
|
|
# executables being tested: `system bin/"program", "do", "something"`.
|
|
system "false"
|
|
end
|
|
end
|
|
ERB
|
|
# <!-- vale on -->
|
|
end
|
|
end
|
|
end
|