Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load internal cask json v3 #16896

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 35 additions & 8 deletions Library/Homebrew/api/cask.rb
Expand Up @@ -42,17 +42,22 @@ def self.source_download(cask)

sig { returns(T::Boolean) }
def self.download_and_cache_data!
json_casks, updated = Homebrew::API.fetch_json_api_file "cask.jws.json"
if Homebrew::API.internal_json_v3?
json_casks, updated = Homebrew::API.fetch_json_api_file "internal/v3/homebrew-cask.jws.json"
overwrite_cache! T.cast(json_casks, T::Hash[String, T.untyped])
else
json_casks, updated = Homebrew::API.fetch_json_api_file "cask.jws.json"

cache["renames"] = {}
cache["casks"] = json_casks.to_h do |json_cask|
token = json_cask["token"]
cache["renames"] = {}
cache["casks"] = json_casks.to_h do |json_cask|
token = json_cask["token"]

json_cask.fetch("old_tokens", []).each do |old_token|
cache["renames"][old_token] = token
end
json_cask.fetch("old_tokens", []).each do |old_token|
cache["renames"][old_token] = token
end

[token, json_cask.except("token")]
[token, json_cask.except("token")]
end
end

updated
Expand All @@ -79,6 +84,28 @@ def self.all_renames
cache.fetch("renames")
end

sig { returns(Hash) }
def self.tap_migrations
# Not sure that we need to reload here.
unless cache.key?("tap_migrations")
json_updated = download_and_cache_data!
write_names(regenerate: json_updated)
end

cache["tap_migrations"]
end

sig { returns(String) }
def self.tap_git_head
# Note sure we need to reload here.
unless cache.key?("tap_git_head")
json_updated = download_and_cache_data!
write_names(regenerate: json_updated)
end

cache["tap_git_head"]
end

sig { params(regenerate: T::Boolean).void }
def self.write_names(regenerate: false)
download_and_cache_data! unless cache.key?("casks")
Expand Down
14 changes: 7 additions & 7 deletions Library/Homebrew/cask/cask.rb
Expand Up @@ -139,7 +139,7 @@
# The caskfile is needed during installation when there are
# `*flight` blocks or the cask has multiple languages
def caskfile_only?
languages.any? || artifacts.any?(Artifact::AbstractFlightBlock)
@caskfile_only || languages.any? || artifacts.any?(Artifact::AbstractFlightBlock)

Check warning on line 142 in Library/Homebrew/cask/cask.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/cask/cask.rb#L142

Added line #L142 was not covered by tests
end

sig { returns(T.nilable(Time)) }
Expand Down Expand Up @@ -292,15 +292,16 @@
def populate_from_api!(json_cask)
raise ArgumentError, "Expected cask to be loaded from the API" unless loaded_from_api?

@caskfile_only = json_cask[:caskfile_only]
@languages = json_cask.fetch(:languages, [])
@tap_git_head = json_cask.fetch(:tap_git_head, "HEAD")

@ruby_source_path = json_cask[:ruby_source_path]

# TODO: Clean this up when we deprecate the current JSON API and move to the internal JSON v3.
ruby_source_sha256 = json_cask.dig(:ruby_source_checksum, :sha256)
ruby_source_sha256 ||= json_cask[:ruby_source_sha256]
@ruby_source_checksum = { "sha256" => ruby_source_sha256 }
@ruby_source_checksum = if Homebrew::API.internal_json_v3?
{ "sha256" => json_cask.fetch(:ruby_source_sha256) }
else
json_cask[:ruby_source_checksum]
end
end

def to_s
Expand Down Expand Up @@ -361,7 +362,6 @@
# @private
def to_internal_api_hash
api_hash = {
"token" => token,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be included in the hash because it's already the key of each object. Not sure how I missed this before.

"name" => name,
"desc" => desc,
"homepage" => homepage,
Expand Down
7 changes: 6 additions & 1 deletion Library/Homebrew/cask/cask_loader.rb
Expand Up @@ -299,7 +299,12 @@ def load(config:)

json_cask = Homebrew::API.merge_variations(json_cask).deep_symbolize_keys.freeze

cask_options[:tap] = Tap.fetch(json_cask[:tap]) if json_cask[:tap].to_s.include?("/")
# We could probably just default to the core cask tap in all cases without problems.
cask_options[:tap] = if Homebrew::API.internal_json_v3?
CoreCaskTap.instance
elsif json_cask[:tap].to_s.include?("/")
Tap.fetch(json_cask[:tap])
end
apainintheneck marked this conversation as resolved.
Show resolved Hide resolved

user_agent = json_cask.dig(:url_specs, :user_agent)
json_cask[:url_specs][:user_agent] = user_agent[1..].to_sym if user_agent && user_agent[0] == ":"
Expand Down
2 changes: 2 additions & 0 deletions Library/Homebrew/tap.rb
Expand Up @@ -1380,6 +1380,8 @@ def cask_renames
def tap_migrations
@tap_migrations ||= if Homebrew::EnvConfig.no_install_from_api?
super
elsif Homebrew::API.internal_json_v3?
Homebrew::API::Cask.tap_migrations
else
migrations, = Homebrew::API.fetch_json_api_file "cask_tap_migrations.jws.json",
stale_seconds: TAP_MIGRATIONS_STALE_SECONDS
Expand Down
123 changes: 123 additions & 0 deletions Library/Homebrew/test/api/internal_tap_json/cask_spec.rb
@@ -0,0 +1,123 @@
# frozen_string_literal: true

RSpec.describe "Internal Tap JSON -- Cask" do
let(:internal_tap_json) { File.read(TEST_FIXTURE_DIR/"internal_tap_json/homebrew-cask.json").chomp }
let(:tap_git_head) { "b26c1e550a8b7eed2dcd5306ea8f3da3848258b3" }

context "when generating JSON", :needs_macos do
before do
FileUtils.rm_rf CoreCaskTap.instance.path
cp_r(TEST_FIXTURE_DIR/"internal_tap_json/homebrew-cask", Tap::TAP_DIRECTORY/"homebrew")
allow(Cask::Cask).to receive(:generating_hash?).and_return(true)
end

it "creates the expected hash" do
api_hash = CoreCaskTap.instance.to_internal_api_hash
api_hash["tap_git_head"] = tap_git_head # tricky to mock

expect(JSON.pretty_generate(api_hash)).to eq(internal_tap_json)
end
end

context "when loading JSON" do
before do
ENV["HOMEBREW_INTERNAL_JSON_V3"] = "1"
ENV.delete("HOMEBREW_NO_INSTALL_FROM_API")

allow(Homebrew::API).to receive(:fetch_json_api_file)
.with("internal/v3/homebrew-cask.jws.json")
.and_return([JSON.parse(internal_tap_json), false])

# `Tap.tap_migration_oldnames` looks for renames in every
# tap so `CoreTap.tap_migrations` gets called and tries to
# fetch stuff from the API. This just avoids errors.
allow(Homebrew::API).to receive(:fetch_json_api_file)
.with("internal/v3/homebrew-core.jws.json")
.and_return([{ "tap_migrations" => {}, "formulae" => {}, "aliases" => {} }, false])

# To allow `cask_names.txt` to be written to the cache.
(HOMEBREW_CACHE/"api").mkdir

Homebrew::API::Cask.clear_cache
end

it "loads cask renames" do
expect(CoreCaskTap.instance.cask_renames).to eq({
"ankerslicer" => "ankermake",
"autodesk-fusion360" => "autodesk-fusion",
"betterdummy" => "betterdisplay",
"factor-lang" => "factor",
"smlnj-lang" => "smlnj",
})
end

it "loads tap migrations" do
expect(CoreCaskTap.instance.tap_migrations).to eq({
"azure-cli" => "homebrew/core",
"basex" => "homebrew/core",
"borgbackup" => "homebrew/core",
"chronograf" => "homebrew/core",
"consul" => "homebrew/core",
})
end

it "loads tap git head" do
expect(Homebrew::API::Cask.tap_git_head)
.to eq(tap_git_head)
end

context "when loading formulae" do
let(:factor_metadata) do
{
"token" => "factor",
"name" => %w[Factor],
"desc" => "Programming language",
"homepage" => "https://factorcode.org/",
"version" => "0.99",
"ruby_source_path" => "Casks/f/factor.rb",
"ruby_source_checksum" => {
"sha256" => "a0dabe24c67269e5310b47639cf32e74f49959ba1be454b2c072805b1f04c7e5",
},
}
end

let(:smlnj_metadata) do
{
"token" => "smlnj",
"name" => ["Standard ML of New Jersey"],
"desc" => "Compiler for the Standard ML '97 programming language",
"homepage" => "https://www.smlnj.org/",
"version" => "110.99.4",
"ruby_source_path" => "Casks/s/smlnj.rb",
"ruby_source_checksum" => {
"sha256" => "d47f46a88248272314a501741460d42a8c731030912a83ef58d3c7fd1e90034d",
},
}
end

it "loads factor" do
factor = Cask::CaskLoader.load("factor")
expect(factor.to_h).to include(factor_metadata)
expect(factor.sha256).to eq("8a7968b873b5e87c83b5d0f5ddb4d3d76a2460f5e5c14edac6b18fe5957bd7d6")
expect(factor.url.to_s).to eq("https://downloads.factorcode.org/releases/0.99/factor-macosx-x86-64-0.99.dmg")
end

it "loads factor from rename" do
factor = Cask::CaskLoader.load("factor-lang")
expect(factor.to_h).to include(**factor_metadata)
end

it "loads smlnj" do
smlnj = Cask::CaskLoader.load("smlnj")
expect(smlnj.to_h).to include(**smlnj_metadata)
expect(smlnj.sha256).to eq("2bf858017b8ba43a70b30527290ed9fbbc81d9eaac1abeba62469d95392019a3")
expect(smlnj.url.to_s).to eq("http://smlnj.cs.uchicago.edu/dist/working/110.99.4/smlnj-amd64-110.99.4.pkg")
end

it "loads smlnj from rename" do
smlnj = Cask::CaskLoader.load("smlnj-lang")
expect(smlnj.to_h).to include(**smlnj_metadata)
end
end
end
end
16 changes: 6 additions & 10 deletions Library/Homebrew/test/api/internal_tap_json/formula_spec.rb
Expand Up @@ -9,10 +9,10 @@
cp_r(TEST_FIXTURE_DIR/"internal_tap_json/homebrew-core", Tap::TAP_DIRECTORY/"homebrew")

# NOTE: Symlinks can't be copied recursively so we create them manually here.
(Tap::TAP_DIRECTORY/"homebrew/homebrew-core").tap do |core_tap|
mkdir(core_tap/"Aliases")
ln_s(core_tap/"Formula/f/fennel.rb", core_tap/"Aliases/fennel-lang")
ln_s(core_tap/"Formula/p/ponyc.rb", core_tap/"Aliases/ponyc-lang")
CoreTap.instance.path.tap do |core_tap_path|
mkdir(core_tap_path/"Aliases")
ln_s(core_tap_path/"Formula/f/fennel.rb", core_tap_path/"Aliases/fennel-lang")
ln_s(core_tap_path/"Formula/p/ponyc.rb", core_tap_path/"Aliases/ponyc-lang")
end
end

Expand All @@ -37,19 +37,15 @@
# tap so `CoreCaskTap.tap_migrations` gets called and tries to
# fetch stuff from the API. This just avoids errors.
allow(Homebrew::API).to receive(:fetch_json_api_file)
.with("cask_tap_migrations.jws.json", anything)
.and_return([{}, false])
.with("internal/v3/homebrew-cask.jws.json")
.and_return([{ "tap_migrations" => {}, "casks" => {} }, false])

# To allow `formula_names.txt` to be written to the cache.
(HOMEBREW_CACHE/"api").mkdir

Homebrew::API::Formula.clear_cache
end

after do
Homebrew::API::Formula.clear_cache
end
Comment on lines -49 to -51
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already clear all caches after each test.


it "loads tap aliases" do
expect(CoreTap.instance.alias_table).to eq({
"fennel-lang" => "fennel",
Expand Down
@@ -0,0 +1,76 @@
{
"tap_git_head": "b26c1e550a8b7eed2dcd5306ea8f3da3848258b3",
"renames": {
"ankerslicer": "ankermake",
"autodesk-fusion360": "autodesk-fusion",
"betterdummy": "betterdisplay",
"factor-lang": "factor",
"smlnj-lang": "smlnj"
},
"tap_migrations": {
"azure-cli": "homebrew/core",
"basex": "homebrew/core",
"borgbackup": "homebrew/core",
"chronograf": "homebrew/core",
"consul": "homebrew/core"
},
"casks": {
"factor": {
"name": [
"Factor"
],
"desc": "Programming language",
"homepage": "https://factorcode.org/",
"url": "https://downloads.factorcode.org/releases/0.99/factor-macosx-x86-64-0.99.dmg",
"version": "0.99",
"sha256": "8a7968b873b5e87c83b5d0f5ddb4d3d76a2460f5e5c14edac6b18fe5957bd7d6",
"artifacts": [
{
"suite": [
"factor"
]
}
],
"ruby_source_path": "Casks/f/factor.rb",
"ruby_source_sha256": "a0dabe24c67269e5310b47639cf32e74f49959ba1be454b2c072805b1f04c7e5",
"caveats": "To use factor, you may need to add the $APPDIR/factor directory\nto your PATH environment variable, e.g. (for Bash shell):\n export PATH=$APPDIR/factor:\"$PATH\"\n"
},
"smlnj": {
"name": [
"Standard ML of New Jersey"
],
"desc": "Compiler for the Standard ML '97 programming language",
"homepage": "https://www.smlnj.org/",
"url": "http://smlnj.cs.uchicago.edu/dist/working/110.99.4/smlnj-amd64-110.99.4.pkg",
"version": "110.99.4",
"sha256": "2bf858017b8ba43a70b30527290ed9fbbc81d9eaac1abeba62469d95392019a3",
"artifacts": [
{
"uninstall": [
{
"pkgutil": "org.smlnj.amd64.pkg"
}
]
},
{
"pkg": [
"smlnj-amd64-110.99.4.pkg"
]
},
{
"zap": [
{
"delete": "/usr/local/smlnj"
}
]
}
],
"ruby_source_path": "Casks/s/smlnj.rb",
"ruby_source_sha256": "d47f46a88248272314a501741460d42a8c731030912a83ef58d3c7fd1e90034d",
"url_specs": {
"verified": "smlnj.cs.uchicago.edu/"
},
"caveats": "To use smlnj, you may need to add the /usr/local/smlnj/bin directory\nto your PATH environment variable, e.g. (for Bash shell):\n export PATH=/usr/local/smlnj/bin:\"$PATH\"\n"
}
}
}
@@ -0,0 +1,20 @@
cask "factor" do
version "0.99"
sha256 "8a7968b873b5e87c83b5d0f5ddb4d3d76a2460f5e5c14edac6b18fe5957bd7d6"

url "https://downloads.factorcode.org/releases/#{version}/factor-macosx-x86-64-#{version}.dmg"
name "Factor"
desc "Programming language"
homepage "https://factorcode.org/"

livecheck do
url "https://downloads.factorcode.org/releases/"
regex(%r{href=.*?(\d+(?:\.\d+)+)/}i)
end

suite "factor"

caveats do
path_environment_variable "#{appdir}/factor"
end
end