Cross-platform diagnostics.

This commit is contained in:
Maxim Belkin 2018-05-12 11:47:12 -05:00
parent 4ebccf79a8
commit e5435dfeb7
No known key found for this signature in database
GPG Key ID: AC71560D4C5F2338
9 changed files with 195 additions and 116 deletions

View File

@ -480,60 +480,6 @@ module Homebrew
EOS EOS
end end
def check_for_gettext
find_relative_paths("lib/libgettextlib.dylib",
"lib/libintl.dylib",
"include/libintl.h")
return if @found.empty?
# Our gettext formula will be caught by check_linked_keg_only_brews
gettext = begin
Formulary.factory("gettext")
rescue
nil
end
homebrew_owned = @found.all? do |path|
Pathname.new(path).realpath.to_s.start_with? "#{HOMEBREW_CELLAR}/gettext"
end
return if gettext&.linked_keg&.directory? && homebrew_owned
inject_file_list @found, <<~EOS
gettext files detected at a system prefix.
These files can cause compilation and link failures, especially if they
are compiled with improper architectures. Consider removing these files:
EOS
end
def check_for_iconv
find_relative_paths("lib/libiconv.dylib", "include/iconv.h")
return if @found.empty?
libiconv = begin
Formulary.factory("libiconv")
rescue
nil
end
if libiconv&.linked_keg&.directory?
unless libiconv.keg_only?
<<~EOS
A libiconv formula is installed and linked.
This will break stuff. For serious. Unlink it.
EOS
end
else
inject_file_list @found, <<~EOS
libiconv files detected at a system prefix other than /usr.
Homebrew doesn't provide a libiconv formula, and expects to link against
the system version in /usr. libiconv in other prefixes can cause
compile or link failure, especially if compiled with improper
architectures. macOS itself never installs anything to /usr/local so
it was either installed by a user or some other third party software.
tl;dr: delete these files:
EOS
end
end
def check_for_config_scripts def check_for_config_scripts
return unless HOMEBREW_CELLAR.exist? return unless HOMEBREW_CELLAR.exist?
real_cellar = HOMEBREW_CELLAR.realpath real_cellar = HOMEBREW_CELLAR.realpath
@ -571,17 +517,17 @@ module Homebrew
EOS EOS
end end
def check_dyld_vars def check_ld_vars
dyld_vars = ENV.keys.grep(/^DYLD_/) ld_vars = ENV.keys.grep(/^(|DY)LD_/)
return if dyld_vars.empty? return if ld_vars.empty?
values = dyld_vars.map { |var| "#{var}: #{ENV.fetch(var)}" } values = ld_vars.map { |var| "#{var}: #{ENV.fetch(var)}" }
message = inject_file_list values, <<~EOS message = inject_file_list values, <<~EOS
Setting DYLD_* vars can break dynamic linking. Setting DYLD_* or LD_* variables can break dynamic linking.
Set variables: Set variables:
EOS EOS
if dyld_vars.include? "DYLD_INSERT_LIBRARIES" if ld_vars.include? "DYLD_INSERT_LIBRARIES"
message += <<~EOS message += <<~EOS
Setting DYLD_INSERT_LIBRARIES can cause Go builds to fail. Setting DYLD_INSERT_LIBRARIES can cause Go builds to fail.
@ -612,38 +558,6 @@ module Homebrew
EOS EOS
end end
def check_for_multiple_volumes
return unless HOMEBREW_CELLAR.exist?
volumes = Volumes.new
# Find the volumes for the TMP folder & HOMEBREW_CELLAR
real_cellar = HOMEBREW_CELLAR.realpath
where_cellar = volumes.which real_cellar
begin
tmp = Pathname.new(Dir.mktmpdir("doctor", HOMEBREW_TEMP))
begin
real_tmp = tmp.realpath.parent
where_tmp = volumes.which real_tmp
ensure
Dir.delete tmp
end
rescue
return
end
return if where_cellar == where_tmp
<<~EOS
Your Cellar and TEMP directories are on different volumes.
macOS won't move relative symlinks across volumes unless the target file already
exists. Brews known to be affected by this are Git and Narwhal.
You should set the "HOMEBREW_TEMP" environmental variable to a suitable
directory on the same volume as your Cellar.
EOS
end
def check_git_version def check_git_version
# https://help.github.com/articles/https-cloning-errors # https://help.github.com/articles/https-cloning-errors
return unless Utils.git_available? return unless Utils.git_available?
@ -859,21 +773,6 @@ module Homebrew
nil nil
end end
def check_for_non_prefixed_findutils
findutils = Formula["findutils"]
return unless findutils.any_version_installed?
gnubin = %W[#{findutils.opt_libexec}/gnubin #{findutils.libexec}/gnubin]
default_names = Tab.for_name("findutils").with? "default-names"
return if !default_names && (paths & gnubin).empty?
<<~EOS
Putting non-prefixed findutils in your path can cause python builds to fail.
EOS
rescue FormulaUnavailableError
nil
end
def check_for_pydistutils_cfg_in_home def check_for_pydistutils_cfg_in_home
return unless File.exist? "#{ENV["HOME"]}/.pydistutils.cfg" return unless File.exist? "#{ENV["HOME"]}/.pydistutils.cfg"

View File

@ -1 +1,5 @@
require "extend/os/mac/diagnostic" if OS.mac? if OS.mac?
require "extend/os/mac/diagnostic"
elsif OS.linux?
require "extend/os/linux/diagnostic"
end

View File

@ -0,0 +1,33 @@
require "tempfile"
require "utils/shell"
require "os/linux/diagnostic"
module Homebrew
module Diagnostic
class Checks
def check_tmpdir_sticky_bit
message = generic_check_tmpdir_sticky_bit
return if message.nil?
message + <<~EOS
If you don't have administrative privileges on this machine,
create a directory and set the HOMEBREW_TEMP environment variable,
for example:
install -d -m 1755 ~/tmp
#{Utils::Shell.set_variable_in_profile("HOMEBREW_TEMP", "~/tmp")}
EOS
end
def check_xdg_data_dirs
return if ENV["XDG_DATA_DIRS"].to_s.empty?
return if ENV["XDG_DATA_DIRS"].split("/").include?(HOMEBREW_PREFIX/"share")
<<~EOS
Homebrew's share was not found in your XDG_DATA_DIRS but you have
this variable set to include other locations.
Some programs like `vapigen` may not work correctly.
Consider adding Homebrew's share directory to XDG_DATA_DIRS like so:
#{Utils::Shell.prepend_variable_in_profile("XDG_DATA_DIRS", HOMEBREW_PREFIX/"share")}
EOS
end
end
end
end

View File

@ -29,6 +29,21 @@ module Homebrew
]).freeze ]).freeze
end end
def check_for_non_prefixed_findutils
findutils = Formula["findutils"]
return unless findutils.any_version_installed?
gnubin = %W[#{findutils.opt_libexec}/gnubin #{findutils.libexec}/gnubin]
default_names = Tab.for_name("findutils").with? "default-names"
return if !default_names && (paths & gnubin).empty?
<<~EOS
Putting non-prefixed findutils in your path can cause python builds to fail.
EOS
rescue FormulaUnavailableError
nil
end
def check_for_unsupported_macos def check_for_unsupported_macos
return if ARGV.homebrew_developer? return if ARGV.homebrew_developer?
@ -275,6 +290,95 @@ module Homebrew
may not build correctly with a non-/usr/local prefix. may not build correctly with a non-/usr/local prefix.
EOS EOS
end end
def check_for_gettext
find_relative_paths("lib/libgettextlib.dylib",
"lib/libintl.dylib",
"include/libintl.h")
return if @found.empty?
# Our gettext formula will be caught by check_linked_keg_only_brews
gettext = begin
Formulary.factory("gettext")
rescue
nil
end
if gettext&.linked_keg&.directory?
homebrew_owned = @found.all? do |path|
Pathname.new(path).realpath.to_s.start_with? "#{HOMEBREW_CELLAR}/gettext"
end
return if homebrew_owned
end
inject_file_list @found, <<~EOS
gettext files detected at a system prefix.
These files can cause compilation and link failures, especially if they
are compiled with improper architectures. Consider removing these files:
EOS
end
def check_for_iconv
find_relative_paths("lib/libiconv.dylib", "include/iconv.h")
return if @found.empty?
libiconv = begin
Formulary.factory("libiconv")
rescue
nil
end
if libiconv&.linked_keg&.directory?
unless libiconv.keg_only?
<<~EOS
A libiconv formula is installed and linked.
This will break stuff. For serious. Unlink it.
EOS
end
else
inject_file_list @found, <<~EOS
libiconv files detected at a system prefix other than /usr.
Homebrew doesn't provide a libiconv formula, and expects to link against
the system version in /usr. libiconv in other prefixes can cause
compile or link failure, especially if compiled with improper
architectures. macOS itself never installs anything to /usr/local so
it was either installed by a user or some other third party software.
tl;dr: delete these files:
EOS
end
end
def check_for_multiple_volumes
return unless HOMEBREW_CELLAR.exist?
volumes = Volumes.new
# Find the volumes for the TMP folder & HOMEBREW_CELLAR
real_cellar = HOMEBREW_CELLAR.realpath
where_cellar = volumes.which real_cellar
begin
tmp = Pathname.new(Dir.mktmpdir("doctor", HOMEBREW_TEMP))
begin
real_tmp = tmp.realpath.parent
where_tmp = volumes.which real_tmp
ensure
Dir.delete tmp
end
rescue
return
end
return if where_cellar == where_tmp
<<~EOS
Your Cellar and TEMP directories are on different volumes.
macOS won't move relative symlinks across volumes unless the target file already
exists. Brews known to be affected by this are Git and Narwhal.
You should set the "HOMEBREW_TEMP" environmental variable to a suitable
directory on the same volume as your Cellar.
EOS
end
end end
end end
end end

View File

@ -0,0 +1,7 @@
module Homebrew
module Diagnostic
class Checks
alias generic_check_tmpdir_sticky_bit check_tmpdir_sticky_bit
end
end
end

View File

@ -145,11 +145,6 @@ describe Homebrew::Diagnostic::Checks do
end end
end end
specify "#check_dyld_vars" do
ENV["DYLD_INSERT_LIBRARIES"] = "foo"
expect(subject.check_dyld_vars).to match("Setting DYLD_INSERT_LIBRARIES")
end
specify "#check_for_symlinked_cellar" do specify "#check_for_symlinked_cellar" do
begin begin
HOMEBREW_CELLAR.rmtree HOMEBREW_CELLAR.rmtree
@ -165,6 +160,26 @@ describe Homebrew::Diagnostic::Checks do
end end
end end
specify "#check_ld_vars catches LD vars" do
ENV["LD_LIBRARY_PATH"] = "foo"
expect(subject.check_ld_vars).to match("Setting DYLD_\\* or LD_\\* variables")
end
specify "#check_ld_vars catches DYLD vars" do
ENV["DYLD_LIBRARY_PATH"] = "foo"
expect(subject.check_ld_vars).to match("Setting DYLD_\\* or LD_\\* variables")
end
specify "#check_ld_vars catches LD and DYLD vars" do
ENV["LD_LIBRARY_PATH"] = "foo"
ENV["DYLD_LIBRARY_PATH"] = "foo"
expect(subject.check_ld_vars).to match("Setting DYLD_\\* or LD_\\* variables")
end
specify "#check_ld_vars returns success when neither LD nor DYLD vars are set" do
expect(subject.check_ld_vars).to be nil
end
specify "#check_tmpdir" do specify "#check_tmpdir" do
ENV["TMPDIR"] = "/i/don/t/exis/t" ENV["TMPDIR"] = "/i/don/t/exis/t"
expect(subject.check_tmpdir).to match("doesn't exist") expect(subject.check_tmpdir).to match("doesn't exist")

View File

@ -45,4 +45,9 @@ describe Homebrew::Diagnostic::Checks do
expect(subject.check_ruby_version) expect(subject.check_ruby_version)
.to match "Ruby version 1.8.6 is unsupported on 10.12" .to match "Ruby version 1.8.6 is unsupported on 10.12"
end end
specify "#check_dyld_insert" do
ENV["DYLD_INSERT_LIBRARIES"] = "foo"
expect(subject.check_ld_vars).to match("Setting DYLD_INSERT_LIBRARIES")
end
end end

View File

@ -75,19 +75,20 @@ describe Utils::Shell do
it "supports Tcsh" do it "supports Tcsh" do
ENV["SHELL"] = "/bin/tcsh" ENV["SHELL"] = "/bin/tcsh"
expect(subject.prepend_path_in_profile(path)) expect(subject.prepend_path_in_profile(path))
.to start_with("echo 'setenv PATH #{path}:$") .to eq("echo 'setenv PATH #{path}:$PATH' >> #{shell_profile}")
end end
it "supports Bash" do it "supports Bash" do
ENV["SHELL"] = "/bin/bash" ENV["SHELL"] = "/bin/bash"
expect(subject.prepend_path_in_profile(path)) expect(subject.prepend_path_in_profile(path))
.to start_with("echo 'export PATH=\"#{path}:$") .to eq("echo 'export PATH=\"#{path}:$PATH\"' >> #{shell_profile}")
end end
it "supports Fish" do it "supports Fish" do
ENV["SHELL"] = "/usr/local/bin/fish" ENV["SHELL"] = "/usr/local/bin/fish"
ENV["fish_user_paths"] = "/some/path"
expect(subject.prepend_path_in_profile(path)) expect(subject.prepend_path_in_profile(path))
.to start_with("echo 'set -g fish_user_paths \"#{path}\" $fish_user_paths' >>") .to eq("echo 'set -g fish_user_paths \"#{path}\" $fish_user_paths' >> #{shell_profile}")
end end
end end
end end

View File

@ -40,6 +40,17 @@ module Utils
SHELL_PROFILE_MAP.fetch(preferred, "~/.bash_profile") SHELL_PROFILE_MAP.fetch(preferred, "~/.bash_profile")
end end
def set_variable_in_profile(variable, value)
case preferred
when :bash, :ksh, :sh, :zsh, nil
"echo 'export #{variable}=#{sh_quote(value)}' >> #{profile}"
when :csh, :tcsh
"echo 'setenv #{variable} #{csh_quote(value)}' >> #{profile}"
when :fish
"echo 'set -gx #{variable} #{sh_quote(value)}' >> #{profile}"
end
end
def prepend_path_in_profile(path) def prepend_path_in_profile(path)
case preferred case preferred
when :bash, :ksh, :sh, :zsh, nil when :bash, :ksh, :sh, :zsh, nil