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

Check flavour to prevent mixed architectures #9126

Closed
wants to merge 1 commit into from
Closed

Check flavour to prevent mixed architectures #9126

wants to merge 1 commit into from

Conversation

claui
Copy link
Contributor

@claui claui commented Nov 13, 2020

  • 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. Extensively hand-tested on all most code paths that came to mind, both at least on Apple Silicon (with and without Rosetta) for now and on an Intel-only Mac.
  • Have you successfully run brew style with your changes locally?
  • Have you successfully run brew tests with your changes locally? Massive amount of errors on my Intel Mac but all of them due to flaky tests.
  • Have you successfully run brew man locally and committed any changes?

Update: Rewritten to accommodate the feedback.

What it does

  • Introduce a read-only HOMEBREW_FLAVOUR environment variable that tells us under which architecture a given Homebrew installation can be run.

  • Prevent mixing-and-matching architectures in a single Homebrew installation by asserting the current architecture to be equal to HOMEBREW_FLAVOUR the saved Homebrew flavour.

  • Give clear and useful guidance when called with the wrong architecture.

  • Display HOMEBREW_FLAVOUR in brew config for diagnostic purposes if it’s set.

Rationale

  • We must keep users from mixing-and-matching architectures in a single Homebrew installation, be it deliberately or accidentally, even if the user has chosen a non-standard installation location.

  • While Homebrew already compares prefixes at formula install time, the check at hand is more general and works with non-standard install locations.

  • It’s important to prevent mixed binary code in native Ruby gems. Therefore we need to check not only formula installations but all Homebrew commands that may install or update a Ruby gem.

How it’s implemented

  • The flavour of the Homebrew installation is stored in the Git config variable homebrew.flavour.

  • Possible values of homebrew.flavour are arm64, x86_64 and not set.

  • homebrew.flavour being not set means that Homebrew either couldn’t determine the flavour yet or that it isn’t running under macOS.

  • Expose both HOMEBREW_MACOS_ARCHITECTURE and HOMEBREW_FLAVOUR to Homebrew. This is so upgrade.sh can figure out and pin a value if it’s not installed in a standard location, and so brew config can display it for diagnostic purposes.

Compatibility

  • Work with non-standard install locations. This is implemented through picking the current architecture on the initial brew update invocation, then persisting it as a Git config value on the Homebrew repository.

  • Work with our alternative installation method, where the directory tree is typically not a Git repository at install time.

  • Works as expected when running on OSes other than macOS.

  • Works on older macOS versions, which do not have the CFBundleIsArchitectureLoadable yet.

  • Works as expected when running on Intel-only Macs (but still provides HOMEBREW_FLAVOUR in that case).

  • Always allow the -- (dash-dash) commands, brew config and brew shellcheck. Only check formula installation, uninstallation and upgrading.

Performance considerations

  • Do not call git config unless possible and absolutely necessary.

  • Do not try to detect Rosetta 2 unless absolutely necessary (i.e.
    only while preparing an error message).

@claui claui added features New features usability Usability of Homebrew/brew labels Nov 13, 2020
@MikeMcQuaid
Copy link
Member

MikeMcQuaid commented Nov 13, 2020

This seems to overlap significantly with #9117 including duplicating constants in multiple locations.

It feels overkill to not allow e.g. brew info to be run under the incorrect architecture. What we care about is installations not conflicting.

I'm not sure it's necessary to verify the architecture outside of install time or restrict the architecture for a given installation that isn't in the default location for the other (given we warn and provide the correct prefix to users anyway).

Where I can see a potential for extension, if we really need it, would be having brew doctor check the architecture of everything that's installed (rather than just the dependencies) and complain if anything doesn't match the current architecture (kinda like how we complain if e.g. formulae are deleted).

  • Massive amount of errors on my Intel Mac but all of them due to flaky tests.

It would be good to debug and attempt to resolve these.

@claui
Copy link
Contributor Author

claui commented Nov 13, 2020

This seems to overlap significantly with #9117 including duplicating constants in multiple locations.

Out of a total of six points which make up #9117, this PR improves on the following two:

  • Check the architecture of (newly installed) dependencies and ensure they are using the correct architecture.

More thorough and more general checks would be helpful for several reasons, which I explained in the Rationale section above.

  • Set and document the expected default prefix for macOS Intel Homebrew, macOS ARM Homebrew (/opt/homebrew) and Homebrew on Linux

It feels overkill to now allow e.g. brew info to be run under the incorrect architecture.

Not sure what you mean. This PR doesn’t allow brew info to be run under the incorrect architecture.

I'm not sure it's necessary to verify the architecture outside of install time

I don’t see how this statement fits together with your statement a few lines above, i. e. that you’d rather not allow brew info to be run under the incorrect architecture. What am I missing?

or restrict the architecture for a given installation that isn't in the default location for the other (given we warn and provide the correct prefix to users anyway).

In our Slack discussions about handling the microarchitecture, I’ve learned from you personally how important it is to have a homogenous Homebrew installation, and how neither a prefix, a Cellar, nor binary Ruby gem extensions should be mixed-and-matched.

Learning that, I changed my mind, admitted in our channel that you were right, dismissed all of my work done on my PR-in-progress, and later went on to design and implement it to match your preferred way (as I’ve perceived it). I feel a little surprised to learn it’s somehow less important now.

Where I can see a potential for extension, if we really need it, would be having brew doctor check the architecture of everything that's installed (rather than just the dependencies) and complain if anything doesn't match the current architecture (kinda like how we complain if e.g. formulae are deleted).

If we can prevent an obvious error beyond the shadow of a doubt, why would we rather diagnose and fix it after the fact?

@claui
Copy link
Contributor Author

claui commented Nov 13, 2020

including duplicating constants in multiple locations

@MikeMcQuaid This is a good and valid point, and I’ll be happy to address it once I completely understand all of what #9117 does. Can you help me understand (grammatically) what the following messages are trying to say?

Cannot install in Homebrew on Intel processor in ARM default prefix (#{HOMEBREW_PREFIX})!

Cannot install in Homebrew on ARM processor in Intel default prefix (#{HOMEBREW_PREFIX})!

Edit:
Specifically, what I’m currently failing to understand from a code contributor’s point of view:

  • what the thing is that’s being installed (could be a formula but not sure? Does the code path in install.rb set up Homebrew itself, kind of after the dedicated installer ran? Can’t make it out from the context.)

  • what “in Homebrew on Intel processor” “in Homebrew on ARM processor” grammatically means. Does it mean a) you can’t install Homebrew itself in that prefix? (in case the install.rb code path is used to set up Homebrew itself) or b) you can’t install a formula into Homebrew while on some processor and Homebrew lives at some mismatching default prefix?

@MikeMcQuaid
Copy link
Member

Not sure what you mean. This PR doesn’t allow brew info to be run under the incorrect architecture.
I don’t see how this statement fits together with your statement a few lines above, i. e. that you’d rather not allow brew info to be run under the incorrect architecture. What am I missing?

@claui Sorry, I edited that ASAP but I guess you missed it (from now to not). I think it's fine for anything except brew install to be run under the wrong architecture.

nor binary Ruby gem extensions should be mixed-and-matched.

This is something else we could avoid being mismatched through e.g. utils/gems.rb and vendor-install.sh.

Learning that, I changed my mind, admitted in our channel that you were right, dismissed all of my work done on my PR-in-progress, and later went on to design and implement it to match your preferred way (as I’ve perceived it). I feel a little surprised to learn it’s somehow less important now.

I was not aware you had a PR in progress, sorry. Similarly, I'm sorry that my communication of preferences were not done better.

If we can prevent an obvious error beyond the shadow of a doubt, why would we rather diagnose and fix it after the fact?

I don't think we need to prevent the usage of Homebrew entirely and block it from even running (besides the cases where it's in a known-wrong prefix) under the incorrect architecture.

Can you help me understand (grammatically) what the following messages are trying to say?

Cannot install in Homebrew on Intel processor in ARM default prefix (#{HOMEBREW_PREFIX})!
Cannot install in Homebrew on ARM processor in Intel default prefix (#{HOMEBREW_PREFIX})!

We don't want to allow installation/reinstallation/upgrade of any formulae in the "wrong" default prefix for the architecture.

  • what the thing is that’s being installed (could be a formula but not sure? Does the code path in install.rb set up Homebrew itself, kind of after the dedicated installer ran? Can’t make it out from the context.)

It is run before install/upgrade/reinstall.

  • you can’t install a formula into Homebrew while on some processor and Homebrew lives at some mismatching default prefix?

This.


To hopefully front-load some of this I'll discuss some more of the PR body:

What it does

  • Introduce a read-only HOMEBREW_FLAVOUR environment variable that tells us under which architecture a given Homebrew installation can be run.
  • Display HOMEBREW_FLAVOUR in brew config for diagnostic purposes if it’s set.

I'd like to avoid user configuration here until we feel it's needed from user issues/requests/confusion.

  • Prevent mixing-and-matching architectures in a single Homebrew installation by asserting the current architecture to be equal to HOMEBREW_FLAVOUR.

I see the existing install check and, if desired, an additional brew doctor (diagnostic.rb) check to ensure all architectures match to be expected.

  • Give clear and useful guidance when called with the wrong architecture.

I'm game to have any existing messaging changed.

Rationale

  • We must keep users from mixing-and-matching architectures in a single Homebrew installation, be it deliberately or accidentally, even if the user has chosen a non-standard installation location.
  • While Homebrew already compares prefixes at formula install time, the check at hand is more general and works with non-standard install locations.

I believe this is handled sufficiently by the existing code. Additional extensions should be in brew doctor.

  • It’s important to prevent mixed binary code in native Ruby gems. Therefore we need to check not only formula installations but all Homebrew commands that may install or update a Ruby gem.

Logic could be altered in utils/gems to select the correct architecture. That said, I'm not sure this is a problem; Ruby should be able to select the correct native extension at runtime? If not, I agree we should guard our gem installation to ensure it's for the correct architecture.

Compatibility

  • Always allow the -- (dash-dash) commands, brew config and brew shellcheck.

I think we should allow all commands except brew install/upgrade/reinstall.

Performance considerations

  • Do not try to detect Rosetta 2 unless absolutely necessary.

I agree with not detecting it at all. We could, if necessary, do so when we're already going to display an error to display a better one.

@claui
Copy link
Contributor Author

claui commented Nov 13, 2020

@MikeMcQuaid Did a complete rewrite, trying to incorporate most of the feedback you’ve given, in the hope that it’s going to meet the requirements better.

**What it does**

- Prevent mixing-and-matching architectures in a single Homebrew
  installation by asserting the current architecture to be equal
  to the saved Homebrew flavour.

- Give clear and useful guidance when called with the wrong
  architecture.

**Rationale**

- We must keep users from mixing-and-matching architectures in a
  single Homebrew installation, be it deliberately or accidentally,
  even if the user has chosen a non-standard installation location.

- While Homebrew already compares prefixes at formula install time,
  the check at hand is more general and works with non-standard
  install locations.

**How it’s implemented**

- The flavour of the Homebrew installation is stored in the Git
  config variable `homebrew.flavour`.

- Possible values of `homebrew.flavour` are `arm64`, `x86_64` and
  not set.

- `homebrew.flavour` being not set means that Homebrew either
  couldn’t determine the flavour yet or that it isn’t running under
  macOS.

**Compatibility**

- Work with non-standard install locations. This is implemented
  through picking the current architecture on the initial
  `brew update` invocation, then persisting it as a Git config
  value on the Homebrew repository.

- Work with our alternative installation method, where the
  directory tree is typically not a Git repository at install time.

- Works as expected when running on OSes other than macOS.

- Works on older macOS versions, which do not have the
  `CFBundleIsArchitectureLoadable` yet.

- Works as expected when running on Intel-only Macs.

- Only check formula installation, uninstallation and upgrading.

**Performance considerations**

- Do not try to detect Rosetta 2 unless absolutely necessary (i.e.
  only while preparing an error message).
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 much better, thanks @claui!

Comment on lines +26 to +28
HOMEBREW_DEFAULT_PREFIX="/usr/local"
HOMEBREW_DEFAULT_TEMP="/private/tmp"
HOMEBREW_MACOS_ARM_DEFAULT_PREFIX="/opt/homebrew"
Copy link
Member

Choose a reason for hiding this comment

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

If these stay around given other comments after this:

  • they need to be used in global.rb so that their definitions aren't duplicated
  • a definition should be present for Linux

Comment on lines +48 to +58
if [[ -n "${HOMEBREW_MACOS_VERSION}" ]] &&
[[ -z "$(git config --get homebrew.flavour 2>/dev/null)" ]]
then
if [[ "${HOMEBREW_PREFIX}" = "${HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}" ]]; then
git config --replace-all homebrew.flavour "arm64" 2>/dev/null
elif [[ "${HOMEBREW_PREFIX}" = "${HOMEBREW_DEFAULT_PREFIX}" ]]; then
git config --replace-all homebrew.flavour "x86_64" 2>/dev/null
else
git config --replace-all homebrew.flavour "$(uname -m)" 2>/dev/null
fi
fi
Copy link
Member

Choose a reason for hiding this comment

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

  • feels like this should be in brew.sh (or even install.rb) rather than update.sh
  • would be good to set a variable inside the if/elif to DRY up the repeated git config --replace-all homebrew.flavour
  • feels like this should be inferred based on previously/newly installed formulae rather than the current architecture on existing installs (to avoid a single Rosetta/non-Rosetta run setting these incorrectly)

return
end

error_title = "This Homebrew installation in #{HOMEBREW_PREFIX} only works on #{homebrew_flavour} processors."
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
error_title = "This Homebrew installation in #{HOMEBREW_PREFIX} only works on #{homebrew_flavour} processors."
error_title = "This Homebrew installation in #{HOMEBREW_PREFIX} only works on #{homebrew_flavour} processors!"

Comment on lines +53 to +55
To use it, you can:
- run your Terminal from Rosetta 2 or
- run 'arch -x86_64 brew' instead of 'brew'.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
To use it, you can:
- run your Terminal from Rosetta 2 or
- run 'arch -x86_64 brew' instead of 'brew'.
To use it, you can choose between:
- running your Terminal from Rosetta 2
- running 'arch -x86_64 brew' instead of 'brew'.

the inline or felt weird

brew bundle dump
def check_flavour_matches_architecture
homebrew_flavour = HOMEBREW_REPOSITORY.cd do
Utils.popen_read("git", "config", "--get", "homebrew.flavour").chomp.presence
Copy link
Member

Choose a reason for hiding this comment

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

Could consider passing this as a global variable, too, so it only needs read once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did that in my first draft, which:

[introduced] a read-only HOMEBREW_FLAVOUR environment variable that tells us under which architecture a given Homebrew installation can be run.

I removed it after your comment that said:

I'd like to avoid user configuration here until we feel it's needed from user issues/requests/confusion.

That environment variable wasn’t user configuration. It was read-only, would be set by Homebrew, ignoring whatever its initial value is. Its purpose was to pass it as a global variable so it only needs to be read once. I removed it anyway according to your request.

So you’d prefer me to add that variable back, right?

Copy link
Member

Choose a reason for hiding this comment

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

That environment variable wasn’t user configuration. It was read-only, would be set by Homebrew, ignoring whatever its initial value is. Its purpose was to pass it as a global variable so it only needs to be read once. I removed it anyway according to your request.

I didn't request you remove it, I requested we avoid it being user configurable as it wasn't clear from the proposal whether it would be or not.

So you’d prefer me to add that variable back, right?

Yes, I think it would be worth considering if it aligns with the fix for #9126 (comment)

#9126 (comment) is another global variable that should be passed from Bash into Ruby to avoid duplication if it's needed there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@MikeMcQuaid Ok, thanks. Will look for the old commit, restore it and apply your suggestions.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks!

@sjackman
Copy link
Member

Bike shedding here, but I'd prefer to name it homebrew.cpu rather than homebrew.flavour.

@MikeMcQuaid
Copy link
Member

Bike shedding here, but I'd prefer to name it homebrew.cpu rather than homebrew.flavour.

homebrew.arch preferred here but I'd also take cpu over flavour.

@sjackman
Copy link
Member

HOMEBREW_PROCESSOR="$(uname -m)"

homebrew.processor for consistency? I do like my foolish consistency. I'm also fine with homebrew.arch.

@MikeMcQuaid
Copy link
Member

homebrew.processor for consistency? I do like my foolish consistency. I'm also fine with homebrew.arch.

Being even more pedantic, though: the processor isn't changing, is it?

@sjackman
Copy link
Member

homebrew.processor for consistency? I do like my foolish consistency. I'm also fine with homebrew.arch.

Being even more pedantic, though: the processor isn't changing, is it?

I think I follow your meaning, that the physical processor is not somehow physically morphing when you run Rosetta. Nonetheless HOMEBREW_PROCESSOR and uname -m do change under Rosetta.

@MikeMcQuaid
Copy link
Member

@claui Any news here? Thanks!

1 similar comment
@MikeMcQuaid
Copy link
Member

@claui Any news here? Thanks!

@claui
Copy link
Contributor Author

claui commented Dec 28, 2020

Bike shedding here, but I'd prefer to name it homebrew.cpu rather than homebrew.flavour.

homebrew.arch preferred here but I'd also take cpu over flavour.

@sjackman @MikeMcQuaid processor is already in use for the actual architecture.
Flavour would refer to the expected architecture.

Will rename flavour to expected_arch and see if that sticks.

@MikeMcQuaid
Copy link
Member

@sjackman @MikeMcQuaid processor is already in use for the actual architecture.
Flavour would refer to the expected architecture.

I think it would be good to use the same name for both and, if needed, use REQUESTED, EXPECTED or similar in the variable to clarify.

@BrewTestBot
Copy link
Member

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@BrewTestBot BrewTestBot added stale No recent activity and removed stale No recent activity labels Jan 20, 2021
@claui
Copy link
Contributor Author

claui commented Jan 28, 2021

Fyi, meant to work on this but still struggling with my recent Big Sur update.

@BrewTestBot
Copy link
Member

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@BrewTestBot BrewTestBot added the stale No recent activity label Feb 19, 2021
@BrewTestBot
Copy link
Member

Review period ended.

@claui
Copy link
Contributor Author

claui commented Feb 19, 2021

Going to try and rebase this weekend.

@BrewTestBot BrewTestBot removed the stale No recent activity label Feb 19, 2021
@MikeMcQuaid
Copy link
Member

@claui That'd be great. If you do so: can you also address (or comment on why your're not addressing) any pending comments here and ensure CI is green? Thanks!

@BrewTestBot
Copy link
Member

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@BrewTestBot BrewTestBot added the stale No recent activity label Mar 13, 2021
@MikeMcQuaid
Copy link
Member

Passing on this for now to avoid stalebot ping-pong but will happily rereview a new version 👍🏻

@github-actions github-actions bot added the outdated PR was locked due to age label Apr 14, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 14, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
features New features outdated PR was locked due to age stale No recent activity usability Usability of Homebrew/brew
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants