Skip to content

Enable formula-to-cask migrations within the same tap#20800

Merged
MikeMcQuaid merged 1 commit intomainfrom
copilot/fix-f276a286-6c31-40cf-8c41-0990802b9dff
Oct 6, 2025
Merged

Enable formula-to-cask migrations within the same tap#20800
MikeMcQuaid merged 1 commit intomainfrom
copilot/fix-f276a286-6c31-40cf-8c41-0990802b9dff

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Oct 2, 2025

Resolves the issue where tap maintainers could not migrate formulae to casks within their own tap using tap_migrations.json.

Problem

Previously, tap_migrations.json only supported formula-to-cask migrations when the target was the homebrew/cask tap. This prevented third-party tap maintainers from migrating their formulae to casks when switching from building from source to distributing precompiled binaries.

For example, GoReleaser maintainers wanted to migrate users from their formula to a cask within the same goreleaser/tap, but there was no way to automatically handle this migration during brew update.

Solution

Extended the migration detection logic in migrate_tap_migration to check if the migration target is a cask by examining the target tap's cask_tokens, in addition to checking if the migration is to homebrew/cask.

# Before
if new_tap_name.start_with?("homebrew/cask")

# After
if new_tap_name.start_with?("homebrew/cask") || new_tap.cask_tokens.include?(new_name)

Usage

Tap maintainers can now add migrations like this to their tap_migrations.json:

{
  "old-formula": "user/tap/new-cask"
}

When users run brew update, the system will:

  • Detect that the migration target is a cask
  • Automatically unlink the formula and install the cask (if the tap is installed and Caskroom exists)
  • Or provide clear instructions for manual migration

Benefits

  • Backward compatible: All existing migrations continue to work unchanged
  • Minimal change: Only one line of logic modified
  • Safe: No false positives - only detects actual casks in installed taps
  • Universal: Benefits all third-party taps, not just core taps

Testing

  • All existing tests pass
  • New test case added to verify same-tap formula-to-cask migration detection
  • Typecheck and style checks pass
Original prompt

This section details on the original issue you should resolve

<issue_title>Tap migrations should allow to migrate from formula to cask in the same tap</issue_title>
<issue_description>### Verification

Provide a detailed description of the proposed feature

The current tap_migrations.json file allows people to define migrations in or outside a tap. But a scenario where the maintainer starts to distribute prebuild binaries rather than building in a formula is not covered. Therefore it would be useful if tap_migrations.json would also check if the migration is from a formula to a cask and act accordingly.

What is the motivation for the feature?

GoReleaser is now distributing precompiled binaries for brew as casks rather than formulae. But this leaves a lot of users with a formula and a cask with the same name in the same tap, and no way to automatically switch the users.

See: https://github.com/orgs/goreleaser/discussions/5563#discussioncomment-14180870

How will the feature be relevant to at least 90% of Homebrew users?

A lot of smaller Go projects use Goreleaser and distribute their software to brew that way. Making the migration to casks (that better fit the Homebrew model) easier should be better for all Homebrew users.

What alternatives to the feature have been considered?

Just hoping users notice the disabling of the formulae and manually switch to casks.</issue_description>

Comments on the Issue (you are @copilot in this section)

@MikeMcQuaid > Therefore it would be useful if `tap_migrations.json` would also check if the migration is from a formula to a cask and act accordingly.

This is already possible: https://github.com/Homebrew/homebrew-core/blob/b83323dad1849149ebc425b22d95322b66e23360/tap_migrations.json#L31-L32

The actual migration itself happens as part of brew update so that's the code that needs to change here.</comment_new>
<comment_new>@SMillerDev
Yeah, I guess the important part is here:

But this leaves a lot of users with a formula and a cask with the same name in the same tap

I'm planning to have a look to see if I can find it in brew upgrade.</comment_new>
<comment_new>@MikeMcQuaid

I'm planning to have a look to see if I can find it in brew upgrade.

@SMillerDev brew update.

Here's the relevant logic that'd need extended to handle all taps:

new_tap = Tap.fetch(new_tap_name)
# For formulae migrated to cask: Auto-install cask or provide install instructions.
if new_tap_name.start_with?("homebrew/cask")
if new_tap.installed? && (HOMEBREW_PREFIX/"Caskroom").directory?
ohai "#{name} has been moved to Homebrew Cask."
ohai "brew unlink #{name}"
system HOMEBREW_BREW_FILE, "unlink", name
ohai "brew cleanup"
system HOMEBREW_BREW_FILE, "cleanup"
ohai "brew install --cask #{new_name}"
system HOMEBREW_BREW_FILE, "install", "--cask", new_name
ohai <<~EOS
#{name} has been moved to Homebrew Cask.
The existing keg has been unlinked.
Please uninstall the formula when convenient by running:
brew uninstall --force #{name}
EOS
else
ohai "#{name} has been moved to Homebrew Cask.", <<~EOS
To uninstall the formula and install the cask, run:
brew uninstall --force #{name}
brew tap #{new_tap_name}
brew install --cask #{new_name}
EOS
end

FWIW, I believe what most users are ask is "what should I do so my users can just run brew upgrade and it just work?".

@caarlos0 Thanks! Yes, I think this is the right way about thinking about this problem and what should be solved in this issue 👍🏻 </comment_new>

Fixes #20585

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Tap migrations should allow to migrate from formula to cask in the same tap Enable formula-to-cask migrations within the same tap Oct 2, 2025
Copilot AI requested a review from MikeMcQuaid October 2, 2025 16:22
@MikeMcQuaid MikeMcQuaid force-pushed the copilot/fix-f276a286-6c31-40cf-8c41-0990802b9dff branch from 441aa63 to 48f7d56 Compare October 3, 2025 15:26
@MikeMcQuaid MikeMcQuaid marked this pull request as ready for review October 3, 2025 15:26
@MikeMcQuaid MikeMcQuaid enabled auto-merge October 3, 2025 15:26
@MikeMcQuaid MikeMcQuaid added this pull request to the merge queue Oct 6, 2025
Merged via the queue into main with commit e02c5e5 Oct 6, 2025
38 checks passed
@MikeMcQuaid MikeMcQuaid deleted the copilot/fix-f276a286-6c31-40cf-8c41-0990802b9dff branch October 6, 2025 08:34
@gibfahn
Copy link
Copy Markdown
Contributor

gibfahn commented Oct 6, 2025

Does this still work if the formula and tap have the same name? That's the most common scenario I've seen with Goreleaser. I guess you'd have to try something like this in tap_migrations.json:

{
  "myapp": "myapp"
}

@MikeMcQuaid
Copy link
Copy Markdown
Member

@gibfahn not sure, try it and see. if it doesn't work and you need that: shout.

@philrz
Copy link
Copy Markdown

philrz commented Oct 9, 2025

@MikeMcQuaid: Per your last comment, I've got a test tap repo where I'm trying to get this working, so I'd like to take you up on your "shout" offer and maybe it will help others.


Background

Tap repo is https://github.com/philrz/homebrew-tap. I've got an old Formula super.rb at the top level and a newer Cask that was built via GoReleaser under Casks/super.rb. Before there's any tap_migrations.json present, as a fresh user doing brew install philrz/tap/super I'd get the old Formula, and doing brew install --cask philrz/tap/super I could force the install of the Cask instead. But since I've got users out there currently using the old Formula, my goal in following the enhancement tracked in this PR is to hopefully migrate users off the old Formula and onto the new Cask the next time they happen to do a general/wide brew update + brew upgrade cycle.

Attempt

After getting the old Formula installed on a fresh scratch host by doing brew install philrz/tap/super, I followed the tip in @gibfahn's comment above by adding a tap_migrations.json in the top level of the tap repo with the contents:

{
  "super": "super"
}

Now on my scratch host I do:

$ brew --version
Homebrew 4.6.16
Homebrew/homebrew-core (git revision bdc47d7de06; last commit 2025-10-09)
Homebrew/homebrew-cask (git revision 6c0fe67a7dc; last commit 2025-10-09)

$ brew update
==> Updating Homebrew...
Installing from the API is now the default behaviour!
You can save space and time by running:
  brew untap homebrew/core
  brew untap homebrew/cask
Updated 1 tap (philrz/tap).
No changes to formulae or casks.

And a subsequent brew upgrade produces no output, so as a user I'm still stuck on the old Formula. Also, if I try to uninstall at this point I get:

$ brew uninstall super
Error: Invalid tap name: 'super'
/opt/homebrew/Library/Homebrew/tap.rb:60:in 'Tap.fetch'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:608:in 'Cask::CaskLoader.tap_cask_token_type'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:244:in 'Cask::CaskLoader::FromTapLoader.try_new'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:520:in 'block in Cask::CaskLoader::FromNameLoader.try_new'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:520:in 'Array#each'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:520:in 'Enumerable#filter_map'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:520:in 'Cask::CaskLoader::FromNameLoader.try_new'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:641:in 'block in Cask::CaskLoader.for'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:640:in 'Array#each'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:640:in 'Cask::CaskLoader.for'
/opt/homebrew/Library/Homebrew/cask/cask_loader.rb:591:in 'Cask::CaskLoader.load'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:598:in 'Homebrew::CLI::NamedArgs#warn_if_cask_conflicts'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:398:in 'block in Homebrew::CLI::NamedArgs#load_formula_or_cask'
/opt/homebrew/Library/Homebrew/api.rb:373:in 'Homebrew.with_no_api_env_if_needed'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:360:in 'Homebrew::CLI::NamedArgs#load_formula_or_cask'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:350:in 'Homebrew::CLI::NamedArgs#load_and_fetch_full_formula_or_cask'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:87:in 'block in Homebrew::CLI::NamedArgs#to_formulae_and_casks'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:86:in 'Array#each'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:86:in 'Enumerable#flat_map'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:86:in 'Homebrew::CLI::NamedArgs#to_formulae_and_casks'
/opt/homebrew/Library/Homebrew/cli/named_args.rb:306:in 'Homebrew::CLI::NamedArgs#to_kegs_to_casks'
/opt/homebrew/Library/Homebrew/cmd/uninstall.rb:44:in 'Homebrew::Cmd::UninstallCmd#run'
/opt/homebrew/Library/Homebrew/brew.rb:101:in '<main>'
Please report this issue:
  https://docs.brew.sh/Troubleshooting

Can you spot something I did wrong, or offer ideas for debug? Thanks!

@MikeMcQuaid
Copy link
Copy Markdown
Member

@philrz thanks for the reproduction, will take a look when I get a chance.

@gibfahn
Copy link
Copy Markdown
Contributor

gibfahn commented Oct 13, 2025

@philrz I think you need to delete the formula and just leave the cask for migrations to work. From your description I'm not sure whether you did that...

@philrz
Copy link
Copy Markdown

philrz commented Oct 15, 2025

@gibfahn: Thanks for the tip. It's true that I was previously not deleting the old Formula. But I just tested it out (specifically, now I removed the old super.rb Formula at the same time I added the tap_migrations.json) and unfortunately it produced the same results as I described in my last comment above: Stuck on old Formula even after brew update / brew upgrade, and same error when trying to uninstall.

@MikeMcQuaid
Copy link
Copy Markdown
Member

I think you need to delete the formula and just leave the cask for migrations to work.

Yeh, this is currently the case. Otherwise we don't want know whether you want to migrate from formula-to-cask or cask-to-formula.

But I just tested it out (specifically, now I removed the old super.rb Formula at the same time I added the tap_migrations.json) and unfortunately it produced the same results as I described in my last comment above: Stuck on old Formula even after brew update / brew upgrade, and same error when trying to uninstall.

@philrz Could you share the specific tap and ideally specific commits that I can use to test this?

@philrz
Copy link
Copy Markdown

philrz commented Oct 23, 2025

@MikeMcQuaid: Sure, thanks again for being willing to give this a look. The tap is at https://github.com/philrz/homebrew-tap. Right now at commit f8066541c1b913a9210735bcf1661aaa10301fde I've got it in a repro state where the tap_migrations.json and Casks/super.rb are both present but the old Formula at top level super.rb has been removed, i.e., based on what I've learned that's how I'd hope that both users previously that had been on the old Formula and brand new users would get steered to the Cask. In this state if I try either brew install philrz/tap/super or brew install --cask philrz/tap/super it fails with a different error (I'll spare you the paste since it sounds like you're game to see it for yourself). You can find the old super.rb Formula at the prior commit at https://raw.githubusercontent.com/philrz/homebrew-tap/77b2f173e55ccd5e193fb20bd08b076a17699b49/super.rb. Let me know if there's anything else I can do to prep things as you need to see them.

@MikeMcQuaid
Copy link
Copy Markdown
Member

@philrz Thanks! #20932 at least fixes both of these errors. Let me know how you get on after that is merged.

@philrz
Copy link
Copy Markdown

philrz commented Nov 1, 2025

Hi again @MikeMcQuaid! Thanks for the help thus far.

I noticed that Homebrew 4.6.19 was released and it includes those fixes, so I just tested it out. Indeed, I no longer see those errors, but I still can't seem to get the migration to work as envisioned here and using the migration config described here.

I've left my test repo in a similar state as described in my last comment, though the commit hash at tip of main now happens to be 8f5812956f2f9064b98834a9c8a2a8da9ee70e85. The setup for my latest test is once again on a scratch host where:

  1. I've previously had the old super.rb Formula installed
  2. Then that super.rb Formula was removed from the repo and that tap_migrations.json added
  3. I then attempted the following steps on the scratch host that I hoped would move me off the installed Formula to the Cask

Instead of the migration I saw the following output:

$ brew --version
Homebrew 4.6.19
Homebrew/homebrew-core (git revision 6203cb9fb1f; last commit 2025-11-01)
Homebrew/homebrew-cask (git revision 501e2eab38b; last commit 2025-11-01)

$ brew update
==> Updating Homebrew...
Installing from the API is now the default behaviour!
You can save space and time by running:
  brew untap homebrew/core
  brew untap homebrew/cask
Updated 1 tap (philrz/tap).
No changes to formulae or casks.

$ brew upgrade
Error: No available formula with the name "philrz/tap/super".

Would appreciate if you have any ideas for other things to try in the migrations file or if there's maybe more issues lurking in here. Thanks again!

@caarlos0
Copy link
Copy Markdown

caarlos0 commented Nov 2, 2025

just noting that I'm having the same issue as @philrz in both these commits:

when I try to upgrade, I get this:

$ HOMEBREW_AUTO_UPDATE_SECS=1 brew upgrade
Error: No available formula with the name "caarlos0/tap/svu". Did you mean caarlos0/tap/gssh or caarlos0/tap/tt?

@MikeMcQuaid
Copy link
Copy Markdown
Member

Thanks @caarlos0 and @philrz. I will circle back on this, I'm just snowed under right now getting Homebrew 4.7.0 out.

@caarlos0
Copy link
Copy Markdown

caarlos0 commented Dec 8, 2025

@MikeMcQuaid gently ping :)

@MikeMcQuaid
Copy link
Copy Markdown
Member

@caarlos0 Still on my list, still snowed under, sorry :)

@MikeMcQuaid
Copy link
Copy Markdown
Member

if someone can open a new issue with reproduction steps that would help get it more visibility

@philrz
Copy link
Copy Markdown

philrz commented Dec 10, 2025

@MikeMcQuaid: Per your suggestion I just opened new issue #21207 that contains repro steps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tap migrations should allow to migrate from formula to cask in the same tap

6 participants