From 342d39e8a4472cd9e9137bbcc64fbb657806d0c9 Mon Sep 17 00:00:00 2001 From: botantony Date: Sun, 30 Mar 2025 00:46:53 +0100 Subject: [PATCH 1/2] language: add php shebang module Signed-off-by: botantony --- Library/Homebrew/language/php.rb | 43 +++++++++ .../test/language/php/shebang_spec.rb | 91 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 Library/Homebrew/language/php.rb create mode 100644 Library/Homebrew/test/language/php/shebang_spec.rb diff --git a/Library/Homebrew/language/php.rb b/Library/Homebrew/language/php.rb new file mode 100644 index 0000000000..c8ec368022 --- /dev/null +++ b/Library/Homebrew/language/php.rb @@ -0,0 +1,43 @@ +# typed: strict +# frozen_string_literal: true + +module Language + # Helper functions for PHP formulae. + # + # @api public + module PHP + # Helper module for replacing `php` shebangs. + module Shebang + extend T::Helpers + + requires_ancestor { Formula } + + module_function + + # A regex to match potential shebang permutations. + PHP_SHEBANG_REGEX = %r{^#! ?(?:/usr/bin/(?:env )?)?php( |$)} + + # The length of the longest shebang matching `SHEBANG_REGEX`. + PHP_SHEBANG_MAX_LENGTH = T.let("#! /usr/bin/env php ".length, Integer) + + # @private + sig { params(php_path: T.any(String, Pathname)).returns(Utils::Shebang::RewriteInfo) } + def php_shebang_rewrite_info(php_path) + Utils::Shebang::RewriteInfo.new( + PHP_SHEBANG_REGEX, + PHP_SHEBANG_MAX_LENGTH, + "#{php_path}\\1", + ) + end + + sig { params(formula: Formula).returns(Utils::Shebang::RewriteInfo) } + def detected_php_shebang(formula = T.cast(self, Formula)) + php_deps = formula.deps.select(&:required?).map(&:name).grep(/^php(@.+)?$/) + raise ShebangDetectionError.new("PHP", "formula does not depend on PHP") if php_deps.empty? + raise ShebangDetectionError.new("PHP", "formula has multiple PHP dependencies") if php_deps.length > 1 + + php_shebang_rewrite_info(Formula[php_deps.first].opt_bin/"php") + end + end + end +end diff --git a/Library/Homebrew/test/language/php/shebang_spec.rb b/Library/Homebrew/test/language/php/shebang_spec.rb new file mode 100644 index 0000000000..47589e0b61 --- /dev/null +++ b/Library/Homebrew/test/language/php/shebang_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require "language/php" +require "utils/shebang" + +RSpec.describe Language::PHP::Shebang do + let(:file) { Tempfile.new("php-shebang") } + let(:broken_file) { Tempfile.new("php-shebang") } + let(:f) do + f = {} + + f[:php81] = formula "php@8.1" do + url "https://brew.sh/node-18.0.0.tgz" + end + + f[:versioned_php_dep] = formula "foo" do + url "https://brew.sh/foo-1.0.tgz" + + depends_on "php@8.1" + end + + f[:no_deps] = formula "foo" do + url "https://brew.sh/foo-1.0.tgz" + end + + f[:multiple_deps] = formula "foo" do + url "https://brew.sh/foo-1.0.tgz" + + depends_on "php" + depends_on "php@8.1" + end + + f + end + + before do + file.write <<~EOS + #!/usr/bin/env php + a + b + c + EOS + file.flush + + broken_file.write <<~EOS + #!php + a + b + c + EOS + broken_file.flush + end + + after { [file, broken_file].each(&:unlink) } + + describe "#detected_php_shebang" do + it "can be used to replace PHP shebangs" do + allow(Formulary).to receive(:factory).with(f[:php81].name).and_return(f[:php81]) + Utils::Shebang.rewrite_shebang described_class.detected_php_shebang(f[:versioned_php_dep]), file.path + + expect(File.read(file)).to eq <<~EOS + #!#{HOMEBREW_PREFIX/"opt/php@8.1/bin/php"} + a + b + c + EOS + end + + it "can fix broken shebang like `#!php`" do + allow(Formulary).to receive(:factory).with(f[:php81].name).and_return(f[:php81]) + Utils::Shebang.rewrite_shebang described_class.detected_php_shebang(f[:versioned_php_dep]), broken_file.path + + expect(File.read(broken_file)).to eq <<~EOS + #!#{HOMEBREW_PREFIX/"opt/php@8.1/bin/php"} + a + b + c + EOS + end + + it "errors if formula doesn't depend on PHP" do + expect { Utils::Shebang.rewrite_shebang described_class.detected_php_shebang(f[:no_deps]), file.path } + .to raise_error(ShebangDetectionError, "Cannot detect PHP shebang: formula does not depend on PHP.") + end + + it "errors if formula depends on more than one php" do + expect { Utils::Shebang.rewrite_shebang described_class.detected_php_shebang(f[:multiple_deps]), file.path } + .to raise_error(ShebangDetectionError, "Cannot detect PHP shebang: formula has multiple PHP dependencies.") + end + end +end From fc39f14eff5939ef0ea4104e22c70c4655fd192f Mon Sep 17 00:00:00 2001 From: botantony Date: Sun, 30 Mar 2025 00:53:54 +0100 Subject: [PATCH 2/2] .rubocop.yml: add `language/php.rb` file Signed-off-by: botantony --- Library/Homebrew/.rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Library/Homebrew/.rubocop.yml b/Library/Homebrew/.rubocop.yml index 1815ed3974..f79308777f 100644 --- a/Library/Homebrew/.rubocop.yml +++ b/Library/Homebrew/.rubocop.yml @@ -41,6 +41,7 @@ Style/Documentation: - language/java.rb - language/node.rb - language/perl.rb + - language/php.rb - language/python.rb - livecheck/strategy/apache.rb - livecheck/strategy/bitbucket.rb