Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 10 additions & 1 deletion Library/Homebrew/api/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ def self.source_download_path(formula, path, checksum: nil, download_queue: nil,
raise ArgumentError, "API source path must be a relative path within the repository."
end

if checksum.nil? && (sha256 = formula.local_patch_checksums[path])
checksum = Checksum.new(sha256)
end
if checksum.blank?
raise CannotInstallFormulaError,
"#{formula.full_name}: no checksum in API metadata for #{path}.\n" \
"Run `brew update` to refresh the API metadata and try again."
end

path = Pathname(path).cleanpath

git_head = formula.tap_git_head || "HEAD"
Expand All @@ -55,7 +64,7 @@ def self.source_download_path(formula, path, checksum: nil, download_queue: nil,
begin
if !download.symlink_location.exist? || !download.symlink_location.symlink?
download.fetch
elsif checksum
else
download.verify_download_integrity(download.symlink_location)
end
rescue ChecksumMismatchError
Expand Down
92 changes: 69 additions & 23 deletions Library/Homebrew/test/api/formula_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def mock_curl_download(stdout:)
before do
allow(Homebrew::API).to receive(:formula_names).and_return([])
allow(f).to receive_messages(ruby_source_path: "Formula/testball.rb", tap_git_head: "abc123",
ruby_source_checksum: nil)
ruby_source_checksum: Checksum.new(Digest::SHA256.hexdigest("content")))
allow(f).to receive(:tap).and_return(CoreTap.instance)
end

Expand Down Expand Up @@ -203,7 +203,7 @@ def mock_curl_download(stdout:)
before do
allow(Homebrew::API).to receive(:formula_names).and_return([])
allow(f).to receive_messages(ruby_source_path: "Formula/testball.rb", tap_git_head: "abc123",
ruby_source_checksum: nil)
ruby_source_checksum: Checksum.new(Digest::SHA256.hexdigest("content")))
allow(f).to receive(:tap).and_return(CoreTap.instance)
end

Expand Down Expand Up @@ -234,34 +234,80 @@ class Testball < Formula
expect(result.name).to eq("testball")
end

it "loads local patch files from API source cache" do
source_path = source_cache_dir/"Homebrew/homebrew-core/abc123/Formula/testball.rb"
source_path.dirname.mkpath
cached_source_path = cache_dir/"downloads/testball.rb"
cached_source_path.dirname.mkpath
cached_source_path.write <<~RUBY
class Testball < Formula
url "https://brew.sh/testball-0.1.tar.gz"

patch do
file "patches/noop-a.diff"
context "with local patch files" do
let(:patch_contents) { "patch contents" }
let(:patch_path) { source_cache_dir/"Homebrew/homebrew-core/abc123/patches/noop-a.diff" }

before do
source_path = source_cache_dir/"Homebrew/homebrew-core/abc123/Formula/testball.rb"
source_path.dirname.mkpath
cached_source_path = cache_dir/"downloads/testball.rb"
cached_source_path.dirname.mkpath
cached_source_path.write <<~RUBY
class Testball < Formula
url "https://brew.sh/testball-0.1.tar.gz"

patch do
file "patches/noop-a.diff"
end
resource "bar" do
url "https://brew.sh/bar-1.0.tar.gz"
patch do
file "patches/resource.diff"
end
end
end
RUBY
FileUtils.ln_s cached_source_path.relative_path_from(source_path.dirname), source_path
allow(f).to receive_messages(
ruby_source_checksum: Checksum.new(cached_source_path.sha256),
local_patch_checksums: {
"patches/noop-a.diff" => Digest::SHA256.hexdigest(patch_contents),
"patches/resource.diff" => Digest::SHA256.hexdigest(patch_contents),
},
)
allow_any_instance_of(Homebrew::API::SourceDownloadStrategy).to receive(:fetch) do |download|
download.cached_location.dirname.mkpath
download.cached_location.write(patch_contents)
download.create_symlink_to_cached_download(download.cached_location)
end
RUBY
FileUtils.ln_s cached_source_path.relative_path_from(source_path.dirname), source_path
end

it "loads verified local patch files from the API source cache" do
expect(described_class.source_download_formula(f).patchlist.fetch(0).contents).to eq(patch_contents)
end

allow_any_instance_of(Homebrew::API::SourceDownload).to receive(:fetch) do |download|
next if download.symlink_location.basename.to_s != "noop-a.diff"
it "rejects a downloaded patch with a different checksum" do
allow(f).to receive(:local_patch_checksums).and_return(
"patches/noop-a.diff" => Digest::SHA256.hexdigest("different"),
"patches/resource.diff" => Digest::SHA256.hexdigest(patch_contents),
)

cached_patch_path = download.downloader.cached_location
cached_patch_path.dirname.mkpath
cached_patch_path.write("patch contents")
download.downloader.create_symlink_to_cached_download(cached_patch_path)
expect { described_class.source_download_formula(f) }.to raise_error(ChecksumMismatchError)
end

result = described_class.source_download_formula(f)
it "rejects a resource patch with a different checksum" do
allow(f).to receive(:local_patch_checksums).and_return(
"patches/noop-a.diff" => Digest::SHA256.hexdigest(patch_contents),
"patches/resource.diff" => Digest::SHA256.hexdigest("different"),
)

expect(result.patchlist.fetch(0).contents).to eq("patch contents")
expect { described_class.source_download_formula(f) }.to raise_error(ChecksumMismatchError)
end

it "rejects a cached patch with a different checksum" do
described_class.source_download_formula(f)
patch_path.realpath.write("modified")

expect { described_class.source_download_formula(f) }.to raise_error(ChecksumMismatchError)
end

it "rejects patches missing a checksum in API metadata" do
allow(f).to receive(:local_patch_checksums).and_return({})

expect { described_class.source_download_formula(f) }
.to raise_error(CannotInstallFormulaError, /checksum.*patches/)
end
end
end
end
Loading