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

Add cask install receipts #17554

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Add cask install receipts #17554

wants to merge 5 commits into from

Conversation

Rylan12
Copy link
Member

@Rylan12 Rylan12 commented Jun 23, 2024

  • 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?

This PR is my first go at adding a cast install receipt (which I've called Tab to align for formulae) as per #17013.

Here is a sample INSTALL_RECEIPT.json that is generated when I run brew install --cask warp:

{
  "homebrew_version": "4.3.6-71-gd665c42-dirty",
  "loaded_from_api": true,
  "installed_as_dependency": false,
  "installed_on_request": true,
  "time": 1719106768,
  "dependencies": {},
  "arch": "arm64",
  "source": {
    "path": "",
    "tap": "homebrew/cask",
    "tap_git_head": "f755fc333ebe7647fa3988e360d47a82120db032",
    "version": "0.2024.06.18.08.02.stable_03"
  },
  "installed_on": {
    "os": "Macintosh",
    "os_version": "macOS 14",
    "cpu_family": "arm_firestorm_icestorm",
    "xcode": "15.4",
    "clt": "15.3.0.0.1.1708646388",
    "preferred_perl": "5.34"
  },
  "artifacts": [
    {
      "app": [
        "Warp.app"
      ]
    },
    {
      "zap": [
        {
          "trash": [
            "~/Library/Application Support/dev.warp.Warp-Stable",
            "~/Library/Logs/warp.log",
            "~/Library/Preferences/dev.warp.Warp-Stable.plist",
            "~/Library/Saved Application State/dev.warp.Warp-Stable.savedState"
          ]
        }
      ]
    }
  ]
}

It doesn't quite work yet (there are some test failures and some other things still) but I wanted to push this up to start getting feedback on the strategy.

@request-info request-info bot added the needs response Needs a response from the issue/PR author label Jun 23, 2024
@Rylan12 Rylan12 removed the needs response Needs a response from the issue/PR author label Jun 23, 2024
@Homebrew Homebrew deleted a comment from request-info bot Jun 23, 2024
@Rylan12 Rylan12 requested a review from a team June 23, 2024 01:41
@Rylan12 Rylan12 added the cask Homebrew Cask label Jun 23, 2024
Copy link
Contributor

@apainintheneck apainintheneck left a comment

Choose a reason for hiding this comment

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

I think in general the approach looks good. It'd be nice to be able to share logic between the formula and cask tab classes though it's possible that there isn't enough overlap to make that worth it.

There are few use cases for this that I've seen recently and I think they all seem to be covered here.

  1. Storing the tap that a cask was installed from.
  2. Storing the cask version.

Library/Homebrew/cask/tab.rb Outdated Show resolved Hide resolved
Library/Homebrew/cask/tab.rb Outdated Show resolved Hide resolved
@Bo98
Copy link
Member

Bo98 commented Jun 24, 2024

Do you have an example what the tab of a cask with an uninstall DSL looks like?

Idea being we avoid reading the Ruby file entirely when uninstalling, except for flight blocks which the tab should have a boolean or something that indicates those are used.

@Rylan12
Copy link
Member Author

Rylan12 commented Jun 24, 2024

Do you have an example what the tab of a cask with an uninstall DSL looks like?

Here is the tab for slack:

{
  "homebrew_version": "4.3.6-71-ge30aea5-dirty",
  "loaded_from_api": true,
  "installed_as_dependency": false,
  "installed_on_request": true,
  "time": 1719197835,
  "dependencies": {
    "macos": {
      ">=": [
        "10.15"
      ]
    }
  },
  "arch": "arm64",
  "source": {
    "path": "",
    "tap": "homebrew/cask",
    "tap_git_head": "f755fc333ebe7647fa3988e360d47a82120db032",
    "version": "4.39.88"
  },
  "installed_on": {
    "os": "Macintosh",
    "os_version": "macOS 14",
    "cpu_family": "arm_firestorm_icestorm",
    "xcode": "15.4",
    "clt": "15.3.0.0.1.1708646388",
    "preferred_perl": "5.34"
  },
  "artifacts": [
    {
      "uninstall": [
        {
          "quit": "com.tinyspeck.slackmacgap"
        }
      ]
    },
    {
      "app": [
        "Slack.app"
      ]
    },
    {
      "zap": [
        {
          "trash": [
            "~/Library/Application Scripts/com.tinyspeck.slackmacgap",
            "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.tinyspeck.slackmacgap.sfl*",
            "~/Library/Application Support/Slack",
            "~/Library/Caches/com.tinyspeck.slackmacgap*",
            "~/Library/Containers/com.tinyspeck.slackmacgap*",
            "~/Library/Cookies/com.tinyspeck.slackmacgap.binarycookies",
            "~/Library/Group Containers/*.com.tinyspeck.slackmacgap",
            "~/Library/Group Containers/*.slack",
            "~/Library/HTTPStorages/com.tinyspeck.slackmacgap*",
            "~/Library/Logs/Slack",
            "~/Library/Preferences/ByHost/com.tinyspeck.slackmacgap.ShipIt.*.plist",
            "~/Library/Preferences/com.tinyspeck.slackmacgap*",
            "~/Library/Saved Application State/com.tinyspeck.slackmacgap.savedState",
            "~/Library/WebKit/com.tinyspeck.slackmacgap"
          ]
        }
      ]
    }
  ]
}

Idea being we avoid reading the Ruby file entirely when uninstalling, except for flight blocks which the tab should have a boolean or something that indicates those are used.

Right now the format I'm saving the artifacts in is the same that is used in the API, so that should be possible (that's what I anticipated happening). Good note on the boolean to indicate about flight blocks, I'll add that.

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.

Looking good so far, great work @Rylan12!

Library/Homebrew/cask/info.rb Outdated Show resolved Hide resolved
Library/Homebrew/cask/tab.rb Outdated Show resolved Hide resolved
@MikeMcQuaid
Copy link
Member

  • Storing the tap that a cask was installed from.
  • Storing the cask version.

In both of these cases: we should store the same for formulae and in the same format for both (to make parsing either easier).

Idea being we avoid reading the Ruby file entirely when uninstalling, except for flight blocks which the tab should have a boolean or something that indicates those are used.

My understanding is also we hope to be able to eventually deprecate these flight blocks so that only the tab is needed for uninstall.

@Bo98
Copy link
Member

Bo98 commented Jun 24, 2024

My understanding is also we hope to be able to eventually deprecate these flight blocks so that only the tab is needed for uninstall.

Yeah, though key word there is "eventually" and no one's working on it yet so still should have something in the meantime to improve the majority of cases while we work on improving the final few cases that use flight blocks.

Flight block phasing out will likely require multiple distinct enhancements to the uninstall DSL.

@MikeMcQuaid
Copy link
Member

Yeah, though key word there is "eventually" and no one's working on it yet so still should have something in the meantime to improve the majority of cases while we work on improving the final few cases that use flight blocks.

Agreed: just think it's worth considering with the design that we will hopefully not keep this around indefinitely.

@Rylan12 Rylan12 force-pushed the cask-install-receipt branch 2 times, most recently from 336fd06 to a4ea7d8 Compare June 25, 2024 18:05
@Rylan12 Rylan12 marked this pull request as ready for review June 25, 2024 18:06
@Rylan12
Copy link
Member Author

Rylan12 commented Jun 25, 2024

I've pushed changes to have casks use the same Tab class as formulae, just with some different methods occasionally. When creating a new tab via an ambiguous method (i.e. when reading from a file and not calling for_formula, for_keg, for_cask, etc), you should specify whether this is a formula or cask tab (formula will be chosen by default if no selection was made). Then, methods like #to_json will intelligently include the correct fields.

I also updated the info and tab commands to support new features here (like marking casks as installed on request or not). Maybe in the future, brew autoremove can be extended to include casks.

Comment on lines +474 to +477
if uninstall_phase_only &&
!artifact.respond_to?(:uninstall_phase) && !artifact.respond_to?(:post_uninstall_phase)
next
end
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: I think this should be a separate method to make the code more understandable.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it needs to be a separate method, personally. I think it's fairly obvious what's going on here from the naming.

Comment on lines -286 to +287
@sourcefile_path = path
@path = path || CaskLoader.default_path(@token)
@sourcefile_path = path || CaskLoader.default_path(@token)
@path = @sourcefile_path
Copy link
Contributor

Choose a reason for hiding this comment

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

Changes requested: This change needs more thought. The expectation in Cask::Cask is that sourcefile_path points to the Ruby source file and this breaks that assumption.

Copy link
Member

Choose a reason for hiding this comment

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

@apainintheneck What's the status quo here if there's no Ruby source file available?

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: Ideally there would be a better separation of logic here. That could come in the form of a base class that both tabs inherit from or a mixin. As currently implemented, it involves a bunch of checks for the cask and formula versions of the tab logic and methods which are not relevant to one or the other type of tab are present at all times.

Copy link
Member

Choose a reason for hiding this comment

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

I somewhat agree. I think rather than having do_something methods that call do_formula_something or do_cask_something or type: :formula parameters, it'd be up to the caller to call the appropriate method(s).

That may remove the need for separate classes by providing sufficient separation. If not, a base tab class that's inherited as FormulaTab or CaskTab or something feels nicer than two mixins that are only used on a single class.

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.

Great work so far, thanks @Rylan12!

Comment on lines +474 to +477
if uninstall_phase_only &&
!artifact.respond_to?(:uninstall_phase) && !artifact.respond_to?(:post_uninstall_phase)
next
end
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it needs to be a separate method, personally. I think it's fairly obvious what's going on here from the naming.

Comment on lines -286 to +287
@sourcefile_path = path
@path = path || CaskLoader.default_path(@token)
@sourcefile_path = path || CaskLoader.default_path(@token)
@path = @sourcefile_path
Copy link
Member

Choose a reason for hiding this comment

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

@apainintheneck What's the status quo here if there's no Ruby source file available?

Comment on lines +47 to +48
def self.create(formula_or_cask, compiler = nil, stdlib = nil)
return create_from_formula(formula_or_cask, compiler, stdlib) if formula_or_cask.is_a? Formula
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: this continues to only create from formulae, raises an specific exception/error message if provided a cask and we use odeprecated to migrate all callers to use create_from_formula or create_from_cask directly instead.

"installed_as_dependency" => false,
"installed_on_request" => false,
"poured_from_bottle" => false,
"loaded_from_api" => false,
Copy link
Member

Choose a reason for hiding this comment

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

This seems like useful to maintain as a generic attribute.

"time" => Time.now.to_i,
"source_modified_time" => formula.source_modified_time.to_i,
Copy link
Member

Choose a reason for hiding this comment

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

And this.

end
end

# Like {from_file}, but bypass the cache.
def self.from_file_content(content, path)
def self.from_file_content(content, path, type: :formula)
Copy link
Member

Choose a reason for hiding this comment

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

Think similarly this would be better being two methods: from_formula_file_content or from_cask_file_content and we odeprecate from_file_content

Copy link
Member

Choose a reason for hiding this comment

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

I somewhat agree. I think rather than having do_something methods that call do_formula_something or do_cask_something or type: :formula parameters, it'd be up to the caller to call the appropriate method(s).

That may remove the need for separate classes by providing sufficient separation. If not, a base tab class that's inherited as FormulaTab or CaskTab or something feels nicer than two mixins that are only used on a single class.

@miccal miccal closed this Jun 26, 2024
@miccal miccal deleted the cask-install-receipt branch June 26, 2024 11:21
@miccal miccal restored the cask-install-receipt branch June 26, 2024 11:22
@miccal miccal reopened this Jun 26, 2024
@miccal
Copy link
Member

miccal commented Jun 26, 2024

Apologies for closing @Rylan12, my bad :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cask Homebrew Cask
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants