-
-
Notifications
You must be signed in to change notification settings - Fork 10k
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
BottleLoader: Use the formula stored in the bottle #3176
Conversation
Library/Homebrew/formulary.rb
Outdated
bottle_version = Utils::Bottles.resolve_version @bottle_filename | ||
system "tar", "-xf", @bottle_filename, "-C", HOMEBREW_CACHE_FORMULA, | ||
"--strip-components", "3", "#{name}/#{bottle_version}/.brew/#{name}.rb", | ||
err: :close |
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.
It would be good to avoid creating/deleting/writing files in the formulary loader. How about using something like tar -O
to read it from stdout
?
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.
Good suggestion, Mike. Done.
3883eea
to
1320d39
Compare
Library/Homebrew/formulary.rb
Outdated
bottle_version = Utils::Bottles.resolve_version @bottle_filename | ||
path = "#{name}/#{bottle_version}/.brew/#{name}.rb" | ||
contents = Utils.popen_read "tar", "-xO", "-f", @bottle_filename, | ||
"--strip-components", "3", path, err: :close |
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.
I don't want err: :close
to be going throughout the codebase when they are no-ops for most users and when, in this case, the error should never be hit. I'm tempted to say rather than falling back this should raise an exception instead. Thoughts?
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.
Agreed. I'll change it.
Library/Homebrew/formulary.rb
Outdated
formula = if $CHILD_STATUS.success? | ||
Formulary.from_contents name, HOMEBREW_CELLAR/path, contents, spec | ||
else | ||
super |
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.
If this stays as-is: worth printing a Warning
message in this case?
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.
I've added a warning.
4b546eb
to
2654b9f
Compare
Library/Homebrew/formulary.rb
Outdated
formula = if $CHILD_STATUS.success? | ||
Formulary.from_contents name, HOMEBREW_CELLAR/path, contents, spec | ||
else | ||
opoo "This bottle does not contain a formula: #{path}" |
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.
I think this should probably just fail rather than have inconsistent behaviour. If you're loading bottles more than a year old you're going to have other problems.
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.
Agreed. Done.
6e0516c
to
622831b
Compare
Library/Homebrew/formulary.rb
Outdated
@@ -122,14 +122,14 @@ def initialize(bottle_name) | |||
super name, Formulary.path(full_name) | |||
end | |||
|
|||
def get_formula(spec, alias_path: nil) | |||
formula = super | |||
def get_formula(spec, alias_path: nil) # rubocop:disable Lint/UnusedMethodArgument |
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.
Is # rubocop:disable Lint/UnusedMethodArgument
the best way to avoid this brew style
error?
W:125: 27: Unused method argument - alias_path.
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.
Would you prefer…
raise "Unexpected non-nil parameter alias_path" unless alias_path.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.
I think def get_formula(spec, **)
should work.
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.
Yes, that works, but it does alter the behavior of the method for style, in that get_formula(spec, monkey: :banana)
no longer fails. rubocop/rubocop#3130 (comment)
My minor style preference is raise … unless nil?
. I'm fine with **
though.
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.
Done.
I have a failed test to address. I'll get back to you with that later today.
|
Bottle version mismatch | ||
Bottle: #{bottle_file} (#{bottle_version}) | ||
Formula: #{formula.full_name} (#{formula_version}) | ||
This bottle does not contain the formula file: |
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.
How about outputting the bottle file and expected formula path within it?
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.
Done.
Library/Homebrew/formulary.rb
Outdated
def get_formula(spec, alias_path: nil) | ||
formula = super | ||
def get_formula(spec, alias_path: nil) # rubocop:disable Lint/UnusedMethodArgument | ||
bottle_version = Utils::Bottles.resolve_version @bottle_filename |
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.
Can this fail?
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.
If the bottle does not contain a file matching the regex .+/.+/INSTALL_RECEIPT.json
, then yes, it could fail. It currently throws an exception:
Error: undefined method `split' for nil:NilClass
/Users/sjackman/.homebrew/Library/Homebrew/utils/bottles.rb:40:in `resolve_formula_names'
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.
I've added
--- a/Library/Homebrew/utils/bottles.rb
+++ b/Library/Homebrew/utils/bottles.rb
@@ -29,9 +29,11 @@ module Utils
end
def receipt_path(bottle_file)
- Utils.popen_read("tar", "-tzf", bottle_file).lines.map(&:chomp).find do |line|
+ path = Utils.popen_read("tar", "-tzf", bottle_file).lines.map(&:chomp).find do |line|
line =~ %r{.+/.+/INSTALL_RECEIPT.json}
end
+ raise "This bottle does not contain the file INSTALL_RECEIPT.json: #{bottle_file}" if path.nil?
+ path
end
def resolve_formula_names(bottle_file)
Library/Homebrew/formulary.rb
Outdated
contents = Utils.popen_read "tar", "-xO", "-f", @bottle_filename, "--strip-components=3", formula_path | ||
raise BottleFormulaUnavailableError, formula_path unless $CHILD_STATUS.success? | ||
formula = Formulary.from_contents name, HOMEBREW_CELLAR/formula_path, contents, spec | ||
formula.bottle_specification.sha256 @bottle_filename.sha256 => Utils::Bottles.tag |
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.
What happens without this, out of interest? I'm 👍 on it, just curious.
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.
Because the formula stored in the bottle does not have a bottle
block, the bottle
method returns nil
, and causes the error. In principle that error would have also been possible before if the formula in the repo did not contain a bottle block, which is uncommon.
==> Pouring the cached bottle
Error: undefined method `compatible_cellar?' for nil:NilClass
/Users/sjackman/.homebrew/Library/Homebrew/formula_installer.rb:104:in `pour_bottle?'
I'll add a commit to address this issue.
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.
Done.
622831b
to
afac925
Compare
@@ -101,7 +101,7 @@ def pour_bottle?(install_bottle_options = { warn: false }) | |||
return false | |||
end | |||
|
|||
unless bottle.compatible_cellar? | |||
if bottle && !bottle.compatible_cellar? |
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.
unless bottle&.compatible_cellar?
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.
I didn't know about the &.
operator! Fantastic!
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.
Will be enforced by brew style
from Homebrew/homebrew-portable-ruby#50. Wasn't supported in Ruby 2.0.
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.
formula.bottle
is nil
when bottle.compatible_cellar?
is false.
Using formula.bottle_specification.compatible_cellar?
rather
than formula.bottle.compatible_cellar?
avoids this problem
Library/Homebrew/utils/bottles.rb
Outdated
line =~ %r{.+/.+/INSTALL_RECEIPT.json} | ||
end | ||
raise "This bottle does not contain the file INSTALL_RECEIPT.json: #{bottle_file}" if path.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.
unless path
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.
Done.
9d1f214
to
aa98aca
Compare
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.
Looks good. Let me know when you've tested this thoroughly locally and will merge. Thanks!
I found one odd use case: |
731b1b1
to
46fa99c
Compare
Do you think it would be useful to add a |
@sjackman If there's 3 or more locations it would be used: yep, otherwise: nope. |
Masking the formula that is in the git repository in favor of one buried in a binary and, in particular, allowing that to override the checksum, seems like a security risk to me. |
I think the checksum should be able to be set if it's unset if that's needed for internal code but it shouldn't be able to be overridden, I agree. I wonder if it's worth reading the version from the both formulae and using the one in Git if the version/revision/ |
If we patch a critical CVE that would still let someone blithely |
A thought: a warning should be output when loading a formula that exists in the tap from the tab at a newer version. If we’re aware an older version is being installed (which we can be): we should warn. |
Sure. I can do that. |
My most recent commit displays a warning when downgrading a formula. |
ffef94e
to
b67c670
Compare
Adding the |
@@ -101,6 +100,7 @@ def pour_bottle?(install_bottle_options = { warn: false }) | |||
return false | |||
end | |||
|
|||
bottle = formula.bottle_specification |
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.
Why is this needed below, out of interest, and bottle_specification
rather than bottle
?
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.
formula.bottle
returns nil
when compatible_cellar?
is false.
See https://github.com/Homebrew/brew/blob/master/Library/Homebrew/formula.rb#L330
and https://github.com/Homebrew/brew/blob/master/Library/Homebrew/software_spec.rb#L93
We could use formula.bottled?
rather than formula.bottle_specification.compatible_cellar?
. Do you have a preference?
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.
formula.bottled?
would be preferable, yeh.
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.
Done.
@@ -235,6 +235,10 @@ def install | |||
end | |||
raise CannotInstallFormulaError, message | |||
end | |||
if formula.opt_prefix.directory? && | |||
formula.pkg_version < (v = Keg.new(formula.opt_prefix.resolved_path).version) | |||
opoo "Downgrading #{formula.name} from #{v} to #{formula.pkg_version}." |
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.
Sorry, rather than downgrading here would be good to check the tab in the bottle and see if the formula file exists and that one has a newer version.
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.
Ah, got it. Sorry I misunderstood. I've updated the warning.
@@ -6,7 +6,7 @@ def initialize(name = "testball_bottle", path = Pathname.new(__FILE__).expand_pa | |||
stable.bottle do | |||
cellar :any_skip_relocation | |||
root_url "file://#{TEST_FIXTURE_DIR}/bottles" | |||
sha256 "9abc8ce779067e26556002c4ca6b9427b9874d25f0cafa7028e05b5c5c410cb4" => Utils::Bottles.tag | |||
sha256 "d48bbbe583dcfbfa608579724fc6f0328b3cd316935c6ea22f134610aaf2952f" => Utils::Bottles.tag |
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.
Presumably: adding the formula?
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.
Yep
7fc5ca9
to
4d3dbae
Compare
# Warn if a more recent version of this formula is available in the tap. | ||
begin | ||
if formula.pkg_version < (v = Formulary.factory(formula.full_name).pkg_version) | ||
opoo "#{formula.name} #{v} is available and more recent than version #{formula.pkg_version}." |
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.
Use formula.full_name
here? @ilovezfs, does this look OK?
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.
Done.
4d3dbae
to
ab2ba27
Compare
formula.bottle is nil when bottle.compatible_cellar? is false. Use formula.bottle_specification.compatible_cellar? rather than formula.bottle.compatible_cellar?.
Update the SHA-256.
Factor Utils::Bottles.formula_contents out of BottleLoader.
Warn if a more recent version of this formula is available in the tap.
ab2ba27
to
c19cc70
Compare
Thanks again @sjackman! |
Woo hoo! Thanks for merging, Mike! |
brew tests
with your changes locally?When installing a bottle from the local file system or on http, use the formula stored in the bottle.