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
Delay loading from cask source api #14820
Delay loading from cask source api #14820
Conversation
For casks with certain stanzas, *flight and language stanzas in this case, we need to use the caskfile to install them correctly. The JSON API is not an option. This delays loading from the source API until just before we try to install one of these casks. This reduces the number of requests we make to the source API.
Review period will end on 2023-02-28 at 00:00:00 UTC. |
@@ -319,15 +299,21 @@ def load(config:) | |||
# convert generic string replacements into actual ones | |||
artifact = cask.loader.from_h_hash_gsubs(artifact, appdir) | |||
key = artifact.keys.first | |||
send(key, *artifact[key]) | |||
if artifact[key].nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it'd be better to be explicit here but at the moment the only artifacts with null values are *flight
blocks.
@@ -77,7 +77,6 @@ class DSL | |||
:depends_on, | |||
:homepage, | |||
:language, | |||
:languages, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This now gets defined explicitly in the cask class.
@@ -235,31 +229,17 @@ def load(config:) | |||
json_cask = @from_json || Homebrew::API::Cask.all_casks[token] | |||
cask_source = JSON.pretty_generate(json_cask) | |||
|
|||
json_cask = Homebrew::API.merge_variations(json_cask).deep_symbolize_keys | |||
tap = Tap.fetch(json_cask[:tap]) if json_cask[:tap].to_s.include?("/") | |||
json_cask = Homebrew::API.merge_variations(json_cask).deep_symbolize_keys.freeze |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a nice to have thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review period ended. |
Didn't have a chance to have a proper look today but concept sounds good. This overlaps a bit with what I was doing but I hadn't touched any of the cask side yet, so this will be a useful. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, thanks! My only question is about how loading from the source api works when loading an installed cask. Will this overwrite the cask file that was downloaded at the time of installation with the new, current source information? My understanding is that we always write the cask source as-is to the .metadata
directory, so when loading from the installed cask file I'm not sure we ever need to redownload.
(Sorry, reviewing on mobile so not able to test it out)
Library/Homebrew/cask/installer.rb
Outdated
def load_cask_from_source_api! | ||
options = { git_head: @cask.tap_git_head, sha256: @cask.ruby_source_checksum["sha256"] } | ||
cask_source = Homebrew::API::Cask.fetch_source(@cask.token, **options) | ||
@cask = CaskLoader::FromContentLoader.new(cask_source).load(config: @cask.config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to include this to make sure that tap
information is properly accounted for?
@cask = CaskLoader::FromContentLoader.new(cask_source).load(config: @cask.config) | |
@cask = CaskLoader::FromContentLoader.new(cask_source, tap: @cask.tap).load(config: @cask.config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tap doesn't seem to be recorded as metadata for the cask when it gets installed unless I'm missing something. That being said it is referenced once when deciding whether to track install analytics so it probably makes sense to add that logic back in (I had already removed it).
brew/Library/Homebrew/cask/installer.rb
Lines 113 to 115 in 6db7732
if @cask.tap&.should_report_analytics? | |
::Utils::Analytics.report_event(:cask_install, @cask.token, on_request: true) | |
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in b91e93c
You're right about that. We first try to load from the installed cask file and only fallback to the source API if that's unavailable, corrupted or outdated and so can't be loaded correctly. We don't want to fallback to the JSON loaded cask in this specific case. |
This info is used in the installer to decide whether to report analytics.
attr_reader :tap | ||
|
||
def initialize(path) | ||
@tap = Tap.from_path(path) | ||
super(path) | ||
end | ||
|
||
private | ||
|
||
def cask(*args, &block) | ||
super(*args, tap: tap, &block) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove these parts because @tap
is now defined in the FromContentLoader
and passed into the cask constructor from there.
Easy to verify by doing the following. Note that the FromDefaultTapPathLoader
inherits from the FromTapPathLoader
.
/u/l/Homebrew (delay-loading-from-cask-source-api|✔) $ brew irb
==> Interactive Homebrew Shell
Example commands available with: `brew irb --examples`
irb(main):001:0> cask = Cask::CaskLoader::FromDefaultTapPathLoader.new("virtualbox").load(config: nil); nil
=> nil
irb(main):002:0> pp cask.tap; nil
#<Tap:0x00007f9ff2a13530
@alias_reverse_table=nil,
@alias_table=nil,
@cask_dir=
#<Pathname:/usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask/Casks>,
@full_name="Homebrew/homebrew-cask",
@name="homebrew/cask",
@path=#<Pathname:/usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask>,
@repo="cask",
@user="Homebrew">
=> nil
Thanks again @apainintheneck! |
brew style
with your changes locally?brew typecheck
with your changes locally?brew tests
with your changes locally?The idea here is to delay the loading of casks from the source API until the last possible moment. That means either right before installation (during the fetch phase to be precise) or during the uninstallation process if we can't load the original cask file.
As a result of this change I had to modify how info was loaded into the cask object from the API. The git tap head and languages info needed to be passed in as well. I tried reorganizing it by adding the
Cask#populate_from_api!
method but if you all think it's better to just pass those as params to the constructor I'm fine with that too. I also load the*flight
artifacts with empty blocks. This info along with the languages is used to decided whether a cask needs the cask file during installs and uninstalls.There are also a few changes to the cask installer. The big one is that we now try to uninstall using the installed cask file if at all possible with the
brew uninstall
andbrew uninstall --zap
commands. Previously we would just try to uninstall using the most recent cask info. I added a few tests for these changes too. This is necessary to make sure we have the cask file if needed during uninstalls.Marked as draft because I want to do a bit more local testing.👍To test you can try installing and uninstalling casks with
language
or*flight
stanzas.