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

Delay loading from cask source api #14820

Merged

Conversation

apainintheneck
Copy link
Contributor

@apainintheneck apainintheneck commented Feb 26, 2023

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same change?
  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes? Here's an example.
  • Have you successfully run brew style with your changes locally?
  • Have you successfully run brew typecheck with your changes locally?
  • Have you successfully run 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 and brew 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.

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.
@apainintheneck apainintheneck added the cask Homebrew Cask label Feb 26, 2023
@apainintheneck apainintheneck self-assigned this Feb 26, 2023
@BrewTestBot
Copy link
Member

Review period will end on 2023-02-28 at 00:00:00 UTC.

@BrewTestBot BrewTestBot added the waiting for feedback Merging is blocked until sufficient time has passed for review label Feb 26, 2023
@apainintheneck apainintheneck mentioned this pull request Feb 26, 2023
7 tasks
@apainintheneck apainintheneck marked this pull request as ready for review February 27, 2023 00:57
@@ -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?
Copy link
Contributor Author

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,
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 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
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 is just a nice to have thing.

@MikeMcQuaid MikeMcQuaid requested review from Bo98 and Rylan12 and removed request for Bo98 February 27, 2023 13:33
Copy link
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

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

Nice cleanup! Makes sense to me but would like @Bo98 and/or @Rylan12 to 👍🏻 it first.

@BrewTestBot BrewTestBot removed the waiting for feedback Merging is blocked until sufficient time has passed for review label Feb 28, 2023
@BrewTestBot
Copy link
Member

Review period ended.

@Bo98
Copy link
Member

Bo98 commented Feb 28, 2023

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.

Copy link
Member

@Rylan12 Rylan12 left a 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)

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)
Copy link
Member

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?

Suggested change
@cask = CaskLoader::FromContentLoader.new(cask_source).load(config: @cask.config)
@cask = CaskLoader::FromContentLoader.new(cask_source, tap: @cask.tap).load(config: @cask.config)

Copy link
Contributor Author

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).

if @cask.tap&.should_report_analytics?
::Utils::Analytics.report_event(:cask_install, @cask.token, on_request: true)
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in b91e93c

@apainintheneck
Copy link
Contributor Author

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)

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.
Comment on lines -153 to -164
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
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 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

@MikeMcQuaid MikeMcQuaid merged commit d0e03fc into Homebrew:master Feb 28, 2023
@MikeMcQuaid
Copy link
Member

Thanks again @apainintheneck!

@apainintheneck apainintheneck deleted the delay-loading-from-cask-source-api branch March 24, 2023 03:25
@github-actions github-actions bot added the outdated PR was locked due to age label Apr 24, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cask Homebrew Cask outdated PR was locked due to age
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants