Skip to content

Commit

Permalink
node: add shebang rewriting
Browse files Browse the repository at this point in the history
Formulae that depend on `node` sometimes contain files that use a
shebang like `#!/usr/bin/env node` and this can lead to issues when
the `node` in a user's environment isn't brewed `node`.

For example, some node modules are compiled when the formula is built
but if the user's `node` is a different major version than brew's
`node`, the differing `NODE_MODULE_VERSION` can produce an error when
certain parts of the application are used. The formula may build and
test fine and the issue may only become apparent when more of the
application is exercised.

This adds a `Language::Node::Shebang` module (borrowing from the
existing Perl and Python examples), which allows us to use
`rewrite_shebang detected_node_shebang, ...` in formulae to address
this type of issue.
  • Loading branch information
samford committed Aug 12, 2023
1 parent 7044f50 commit 0842a71
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
25 changes: 25 additions & 0 deletions Library/Homebrew/language/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,30 @@ def self.local_npm_install_args
--#{npm_cache_config}
]
end

# Mixin module for {Formula} adding shebang rewrite features.
module Shebang
module_function

# @private
sig { params(node_path: T.any(String, Pathname)).returns(Utils::Shebang::RewriteInfo) }
def node_shebang_rewrite_info(node_path)
Utils::Shebang::RewriteInfo.new(
%r{^#! ?/usr/bin/(?:env )?node( |$)},
21, # the length of "#! /usr/bin/env node "
"#{node_path}\\1",
)
end

sig { params(formula: T.untyped).returns(Utils::Shebang::RewriteInfo) }
def detected_node_shebang(formula = self)
node_deps = formula.deps.map(&:name).grep(/^node(@.+)?$/)

raise ShebangDetectionError.new("Node", "formula does not depend on Node") if node_deps.empty?
raise ShebangDetectionError.new("Node", "formula has multiple Node dependencies") if node_deps.length > 1

node_shebang_rewrite_info(Formula[node_deps.first].opt_bin/"node")
end
end
end
end
9 changes: 9 additions & 0 deletions Library/Homebrew/language/node.rbi
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# typed: strict

module Language
module Node
module Shebang
include Kernel
end
end
end
72 changes: 72 additions & 0 deletions Library/Homebrew/test/language/node/shebang_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

require "language/node"
require "utils/shebang"

describe Language::Node::Shebang do
let(:file) { Tempfile.new("node-shebang") }
let(:node18_f) do
formula "node@18" do
url "https://brew.sh/node-18.0.0.tgz"
end
end
let(:f) do
formula "foo" do
url "https://brew.sh/foo-1.0.tgz"

depends_on "node@18"
end
end

before do
file.write <<~EOS
#!/usr/bin/env node
a
b
c
EOS
file.flush
end

after { file.unlink }

describe "#detected_node_shebang" do
let(:f_no_deps) do
formula "foo" do
url "https://brew.sh/foo-1.0.tgz"
end
end

let(:f_multiple_deps) do
formula "foo" do
url "https://brew.sh/foo-1.0.tgz"

depends_on "node"
depends_on "node@18"
end
end

it "can be used to replace Node shebangs" do
allow(Formulary).to receive(:factory)
allow(Formulary).to receive(:factory).with(node18_f.name).and_return(node18_f)
Utils::Shebang.rewrite_shebang described_class.detected_node_shebang(f), file.path

expect(File.read(file)).to eq <<~EOS
#!#{HOMEBREW_PREFIX/"opt/node@18/bin/node"}
a
b
c
EOS
end

it "errors if formula doesn't depend on node" do
expect { Utils::Shebang.rewrite_shebang described_class.detected_node_shebang(f_no_deps), file.path }
.to raise_error(ShebangDetectionError, "Cannot detect Node shebang: formula does not depend on Node.")
end

it "errors if formula depends on more than one node" do
expect { Utils::Shebang.rewrite_shebang described_class.detected_node_shebang(f_multiple_deps), file.path }
.to raise_error(ShebangDetectionError, "Cannot detect Node shebang: formula has multiple Node dependencies.")
end
end
end

0 comments on commit 0842a71

Please sign in to comment.