brew/Library/Homebrew/formula_creator.rb

260 lines
9.2 KiB
Ruby
Raw Normal View History

rubocop: Use `Sorbet/StrictSigil` as it's better than comments - Previously I thought that comments were fine to discourage people from wasting their time trying to bump things that used `undef` that Sorbet didn't support. But RuboCop is better at this since it'll complain if the comments are unnecessary. - Suggested in https://github.com/Homebrew/brew/pull/18018#issuecomment-2283369501. - I've gone for a mixture of `rubocop:disable` for the files that can't be `typed: strict` (use of undef, required before everything else, etc) and `rubocop:todo` for everything else that should be tried to make strictly typed. There's no functional difference between the two as `rubocop:todo` is `rubocop:disable` with a different name. - And I entirely disabled the cop for the docs/ directory since `typed: strict` isn't going to gain us anything for some Markdown linting config files. - This means that now it's easier to track what needs to be done rather than relying on checklists of files in our big Sorbet issue: ```shell $ git grep 'typed: true # rubocop:todo Sorbet/StrictSigil' | wc -l 268 ``` - And this is confirmed working for new files: ```shell $ git status On branch use-rubocop-for-sorbet-strict-sigils Untracked files: (use "git add <file>..." to include in what will be committed) Library/Homebrew/bad.rb Library/Homebrew/good.rb nothing added to commit but untracked files present (use "git add" to track) $ brew style Offenses: bad.rb:1:1: C: Sorbet/StrictSigil: Sorbet sigil should be at least strict got true. ^^^^^^^^^^^^^ 1340 files inspected, 1 offense detected ```
2024-08-12 10:30:59 +01:00
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "digest"
require "erb"
module Homebrew
2020-08-17 05:53:46 +02:00
# Class for generating a formula from a template.
class FormulaCreator
2023-11-28 18:10:20 +00:00
attr_accessor :name
2023-11-28 18:32:56 +00:00
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
2023-11-28 18:32:56 +00:00
}
def initialize(name, version, tap:, url:, mode:, license:, fetch:, head:)
@name = name
@version = Version.new(version) if version
2023-11-28 16:07:38 +00:00
@tap = Tap.fetch(tap || "homebrew/core")
@url = url
2023-11-28 18:07:32 +00:00
@mode = mode
2023-11-28 15:46:01 +00:00
@license = license
@fetch = fetch
@head = head
end
2023-11-29 18:57:24 +00:00
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) }
2023-11-23 18:04:03 +03:00
def write_formula!
raise ArgumentError, "name is blank!" if @name.blank?
raise ArgumentError, "tap is blank!" if @tap.blank?
2023-11-23 18:12:12 +03:00
path = @tap.new_formula_path(@name)
raise "#{path} already exists" if path.exist?
2023-11-28 15:39:04 +00:00
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
html_doctype_prefix = "<!doctype html"
2025-03-28 07:05:53 +03:00
if File.read(filepath, html_doctype_prefix.length).downcase.start_with?(html_doctype_prefix)
raise "Downloaded URL is not archive"
end
@sha256 = filepath.sha256
end
if @github
@desc = @github["description"]
2025-03-30 18:01:02 +02:00
@homepage = if @github["homepage"].nil?
"https://github.com/#{@github["full_name"]}"
else
@github["homepage"]
end
@license = @github["license"]["spdx_id"] if @github["license"]
end
end
path.dirname.mkpath
2019-10-13 10:09:38 +01:00
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
2020-10-20 12:03:48 +02:00
sig { returns(String) }
def template
2024-04-30 11:10:23 +02:00
# FIXME: https://github.com/errata-ai/vale/issues/818
# <!-- vale off -->
2018-07-11 15:17:40 +02:00
<<~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
2023-11-28 18:07:32 +00:00
<% if @mode == :python %>
2019-09-24 19:34:34 +02:00
include Language::Python::Virtualenv
<% end %>
desc "#{@desc}"
homepage "#{@homepage}"
<% unless @head %>
url "#{@url}"
2023-11-28 15:39:04 +00:00
<% unless @version.detected_from_url? %>
version "#{@version}"
<% end %>
sha256 "#{@sha256}"
<% end %>
2023-11-28 15:46:01 +00:00
license "#{@license}"
<% if @head %>
head "#{@url}"
<% end %>
2023-11-28 18:07:32 +00:00
<% if @mode == :cmake %>
depends_on "cmake" => :build
2023-11-28 18:07:32 +00:00
<% elsif @mode == :crystal %>
2020-06-25 17:17:42 +02:00
depends_on "crystal" => :build
2023-11-28 18:07:32 +00:00
<% elsif @mode == :go %>
2019-09-20 16:09:01 +02:00
depends_on "go" => :build
2023-11-28 18:07:32 +00:00
<% elsif @mode == :meson %>
depends_on "meson" => :build
depends_on "ninja" => :build
2023-11-28 18:07:32 +00:00
<% elsif @mode == :node %>
2020-07-12 21:25:01 -07:00
depends_on "node"
2023-11-28 18:07:32 +00:00
<% elsif @mode == :perl %>
2019-09-25 21:52:16 +02:00
uses_from_macos "perl"
2023-11-28 18:07:32 +00:00
<% elsif @mode == :python %>
depends_on "#{latest_versioned_formula("python")}"
2023-11-28 18:07:32 +00:00
<% elsif @mode == :ruby %>
2020-03-21 15:32:52 +01:00
uses_from_macos "ruby"
2023-11-28 18:07:32 +00:00
<% elsif @mode == :rust %>
2019-09-25 14:29:09 +02:00
depends_on "rust" => :build
2025-02-21 14:53:34 +01:00
<% elsif @mode == :zig %>
depends_on "zig" => :build
2023-11-28 18:07:32 +00:00
<% elsif @mode.nil? %>
# depends_on "cmake" => :build
<% end %>
2025-01-30 12:51:58 +08:00
<% if @mode == :perl || :python || :ruby %>
2019-09-25 21:52:16 +02:00
# Additional dependency
# resource "" do
# url ""
# sha256 ""
# end
<% end %>
def install
2023-11-28 18:07:32 +00:00
<% if @mode == :cmake %>
system "cmake", "-S", ".", "-B", "build", *std_cmake_args
system "cmake", "--build", "build"
system "cmake", "--install", "build"
2023-11-28 18:07:32 +00:00
<% 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
2023-11-28 18:07:32 +00:00
<% elsif @mode == :crystal %>
2020-06-25 17:17:42 +02:00
system "shards", "build", "--release"
bin.install "bin/#{name}"
2023-11-28 18:07:32 +00:00
<% elsif @mode == :go %>
system "go", "build", *std_go_args(ldflags: "-s -w")
2023-11-28 18:07:32 +00:00
<% elsif @mode == :meson %>
system "meson", "setup", "build", *std_meson_args
system "meson", "compile", "-C", "build", "--verbose"
system "meson", "install", "-C", "build"
2023-11-28 18:07:32 +00:00
<% elsif @mode == :node %>
2024-07-25 22:24:09 -07:00
system "npm", "install", *std_npm_args
2020-07-12 21:25:01 -07:00
bin.install_symlink Dir["\#{libexec}/bin/*"]
2023-11-28 18:07:32 +00:00
<% elsif @mode == :perl %>
2019-09-25 21:52:16 +02:00
ENV.prepend_create_path "PERL5LIB", libexec/"lib/perl5"
ENV.prepend_path "PERL5LIB", libexec/"lib"
2024-04-30 11:10:23 +02:00
# Stage additional dependency (`Makefile.PL` style).
2019-09-25 21:52:16 +02:00
# resource("").stage do
# system "perl", "Makefile.PL", "INSTALL_BASE=\#{libexec}"
# system "make"
# system "make", "install"
# end
2024-04-30 11:10:23 +02:00
# Stage additional dependency (`Build.PL` style).
2019-09-25 21:52:16 +02:00
# resource("").stage do
# system "perl", "Build.PL", "--install_base", libexec
# system "./Build"
# system "./Build", "install"
# end
bin.install name
2020-12-28 14:17:36 -08:00
bin.env_script_all_files(libexec/"bin", PERL5LIB: ENV["PERL5LIB"])
2023-11-28 18:07:32 +00:00
<% elsif @mode == :python %>
2019-09-24 19:34:34 +02:00
virtualenv_install_with_resources
2023-11-28 18:07:32 +00:00
<% elsif @mode == :ruby %>
2020-03-21 15:32:52 +01:00
ENV["GEM_HOME"] = libexec
2025-01-30 12:51:58 +08:00
2025-02-01 17:23:02 +08:00
system "bundle", "install", "-without", "development", "test"
2020-03-21 15:32:52 +01:00
system "gem", "build", "\#{name}.gemspec"
2025-01-30 12:51:58 +08:00
system "gem", "install", "\#{name}-\#{version}.gem"
2020-03-21 15:32:52 +01:00
bin.install libexec/"bin/\#{name}"
2020-12-28 14:17:36 -08:00
bin.env_script_all_files(libexec/"bin", GEM_HOME: ENV["GEM_HOME"])
2023-11-28 18:07:32 +00:00
<% elsif @mode == :rust %>
2020-06-22 13:24:41 +02:00
system "cargo", "install", *std_cargo_args
2025-02-21 14:53:34 +01:00
<% 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
2018-07-11 15:17:40 +02:00
ERB
2024-04-30 11:10:23 +02:00
# <!-- vale on -->
end
end
end