diff --git a/Library/Homebrew/cmd/doctor.rb b/Library/Homebrew/cmd/doctor.rb index 142c7fe0e2..fe230c8bca 100644 --- a/Library/Homebrew/cmd/doctor.rb +++ b/Library/Homebrew/cmd/doctor.rb @@ -1,1318 +1,15 @@ -require "cmd/missing" -require "formula" -require "keg" -require "language/python" -require "version" - -class Volumes - def initialize - @volumes = get_mounts - end - - def which(path) - vols = get_mounts path - - # no volume found - if vols.empty? - return -1 - end - - vol_index = @volumes.index(vols[0]) - # volume not found in volume list - if vol_index.nil? - return -1 - end - vol_index - end - - def get_mounts(path = nil) - vols = [] - # get the volume of path, if path is nil returns all volumes - - args = %w[/bin/df -P] - args << path if path - - Utils.popen_read(*args) do |io| - io.each_line do |line| - case line.chomp - # regex matches: /dev/disk0s2 489562928 440803616 48247312 91% / - when /^.+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]{1,3}%\s+(.+)/ - vols << $1 - end - end - end - vols - end -end - -class Checks - ############# HELPERS - # Finds files in HOMEBREW_PREFIX *and* /usr/local. - # Specify paths relative to a prefix eg. "include/foo.h". - # Sets @found for your convenience. - def find_relative_paths(*relative_paths) - @found = %W[#{HOMEBREW_PREFIX} /usr/local].uniq.inject([]) do |found, prefix| - found + relative_paths.map { |f| File.join(prefix, f) }.select { |f| File.exist? f } - end - end - - def inject_file_list(list, str) - list.inject(str) { |s, f| s << " #{f}\n" } - end - ############# END HELPERS - - # Sorry for the lack of an indent here, the diff would have been unreadable. - # See https://github.com/Homebrew/homebrew/pull/9986 - def check_path_for_trailing_slashes - bad_paths = ENV["PATH"].split(File::PATH_SEPARATOR).select { |p| p[-1..-1] == "/" } - return if bad_paths.empty? - inject_file_list bad_paths, <<-EOS.undent - Some directories in your path end in a slash. - Directories in your path should not end in a slash. This can break other - doctor checks. The following directories should be edited: - EOS - end - - # Installing MacGPG2 interferes with Homebrew in a big way - # https://github.com/GPGTools/MacGPG2 - def check_for_macgpg2 - return if File.exist? "/usr/local/MacGPG2/share/gnupg/VERSION" - - suspects = %w[ - /Applications/start-gpg-agent.app - /Library/Receipts/libiconv1.pkg - /usr/local/MacGPG2 - ] - - if suspects.any? { |f| File.exist? f } then <<-EOS.undent - You may have installed MacGPG2 via the package installer. - Several other checks in this script will turn up problems, such as stray - dylibs in /usr/local and permissions issues with share and man in /usr/local/. - EOS - end - end - - # Anaconda installs multiple system & brew dupes, including OpenSSL, Python, - # sqlite, libpng, Qt, etc. Regularly breaks compile on Vim, MacVim and others. - # Is flagged as part of the *-config script checks below, but people seem - # to ignore those as warnings rather than extremely likely breakage. - def check_for_anaconda - return unless which("anaconda") - return unless which("python") - - anaconda = which("anaconda").realpath.dirname - python_binary = Utils.popen_read which("python"), "-c", "import sys; sys.stdout.write(sys.executable)" - python = Pathname.new(python_binary).realpath.dirname - - # Only warn if Python lives with Anaconda, since is most problematic case. - if python == anaconda then <<-EOS.undent - Anaconda is known to frequently break Homebrew builds, including Vim and - MacVim, due to bundling many duplicates of system and Homebrew-available - tools. - - If you encounter a build failure please temporarily remove Anaconda - from your $PATH and attempt the build again prior to reporting the - failure to us. Thanks! - EOS - end - end - - def __check_stray_files(dir, pattern, white_list, message) - return unless File.directory?(dir) - - files = Dir.chdir(dir) do - Dir[pattern].select { |f| File.file?(f) && !File.symlink?(f) } - Dir.glob(white_list) - end.map { |file| File.join(dir, file) } - - inject_file_list(files, message) unless files.empty? - end - - def check_for_stray_dylibs - # Dylibs which are generally OK should be added to this list, - # with a short description of the software they come with. - white_list = [ - "libfuse.2.dylib", # MacFuse - "libfuse_ino64.2.dylib", # MacFuse - "libmacfuse_i32.2.dylib", # OSXFuse MacFuse compatibility layer - "libmacfuse_i64.2.dylib", # OSXFuse MacFuse compatibility layer - "libosxfuse_i32.2.dylib", # OSXFuse - "libosxfuse_i64.2.dylib", # OSXFuse - "libTrAPI.dylib", # TrAPI / Endpoint Security VPN - "libntfs-3g.*.dylib", # NTFS-3G - "libntfs.*.dylib", # NTFS-3G - "libublio.*.dylib", # NTFS-3G - "libUFSDNTFS.dylib", # Paragon NTFS - "libUFSDExtFS.dylib", # Paragon ExtFS - ] - - __check_stray_files "/usr/local/lib", "*.dylib", white_list, <<-EOS.undent - Unbrewed dylibs were found in /usr/local/lib. - If you didn't put them there on purpose they could cause problems when - building Homebrew formulae, and may need to be deleted. - - Unexpected dylibs: - EOS - end - - def check_for_stray_static_libs - # Static libs which are generally OK should be added to this list, - # with a short description of the software they come with. - white_list = [ - "libsecurity_agent_client.a", # OS X 10.8.2 Supplemental Update - "libsecurity_agent_server.a", # OS X 10.8.2 Supplemental Update - "libntfs-3g.a", # NTFS-3G - "libntfs.a", # NTFS-3G - "libublio.a", # NTFS-3G - ] - - __check_stray_files "/usr/local/lib", "*.a", white_list, <<-EOS.undent - Unbrewed static libraries were found in /usr/local/lib. - If you didn't put them there on purpose they could cause problems when - building Homebrew formulae, and may need to be deleted. - - Unexpected static libraries: - EOS - end - - def check_for_stray_pcs - # Package-config files which are generally OK should be added to this list, - # with a short description of the software they come with. - white_list = [ - "fuse.pc", # OSXFuse/MacFuse - "macfuse.pc", # OSXFuse MacFuse compatibility layer - "osxfuse.pc", # OSXFuse - "libntfs-3g.pc", # NTFS-3G - "libublio.pc", # NTFS-3G - ] - - __check_stray_files "/usr/local/lib/pkgconfig", "*.pc", white_list, <<-EOS.undent - Unbrewed .pc files were found in /usr/local/lib/pkgconfig. - If you didn't put them there on purpose they could cause problems when - building Homebrew formulae, and may need to be deleted. - - Unexpected .pc files: - EOS - end - - def check_for_stray_las - white_list = [ - "libfuse.la", # MacFuse - "libfuse_ino64.la", # MacFuse - "libosxfuse_i32.la", # OSXFuse - "libosxfuse_i64.la", # OSXFuse - "libntfs-3g.la", # NTFS-3G - "libntfs.la", # NTFS-3G - "libublio.la", # NTFS-3G - ] - - __check_stray_files "/usr/local/lib", "*.la", white_list, <<-EOS.undent - Unbrewed .la files were found in /usr/local/lib. - If you didn't put them there on purpose they could cause problems when - building Homebrew formulae, and may need to be deleted. - - Unexpected .la files: - EOS - end - - def check_for_stray_headers - white_list = [ - "fuse.h", # MacFuse - "fuse/**/*.h", # MacFuse - "macfuse/**/*.h", # OSXFuse MacFuse compatibility layer - "osxfuse/**/*.h", # OSXFuse - "ntfs/**/*.h", # NTFS-3G - "ntfs-3g/**/*.h", # NTFS-3G - ] - - __check_stray_files "/usr/local/include", "**/*.h", white_list, <<-EOS.undent - Unbrewed header files were found in /usr/local/include. - If you didn't put them there on purpose they could cause problems when - building Homebrew formulae, and may need to be deleted. - - Unexpected header files: - EOS - end - - def check_for_other_package_managers - ponk = MacOS.macports_or_fink - unless ponk.empty? - <<-EOS.undent - You have MacPorts or Fink installed: - #{ponk.join(", ")} - - This can cause trouble. You don't have to uninstall them, but you may want to - temporarily move them out of the way, e.g. - - sudo mv /opt/local ~/macports - EOS - end - end - - def check_for_broken_symlinks - broken_symlinks = [] - - Keg::PRUNEABLE_DIRECTORIES.each do |d| - next unless d.directory? - d.find do |path| - if path.symlink? && !path.resolved_path_exists? - broken_symlinks << path - end - end - end - return if broken_symlinks.empty? - inject_file_list broken_symlinks, <<-EOS.undent - Broken symlinks were found. Remove them with `brew prune`: - EOS - end - - def check_for_unsupported_osx - if !ARGV.homebrew_developer? && OS::Mac.prerelease? then <<-EOS.undent - You are using OS X #{MacOS.version}. - We do not provide support for this pre-release version. - You may encounter build failures or other breakages. - EOS - end - end - - # TODO: distill down into single method definition a la BuildToolsError - if MacOS.version >= "10.9" - def check_for_installed_developer_tools - unless MacOS::Xcode.installed? || MacOS::CLT.installed? then <<-EOS.undent - No developer tools installed. - Install the Command Line Tools: - xcode-select --install - EOS - end - end - - if OS::Mac.prerelease? - def check_xcode_up_to_date - if MacOS::Xcode.installed? && MacOS::Xcode.outdated? - <<-EOS.undent - Your Xcode (#{MacOS::Xcode.version}) is outdated - Please update to Xcode #{MacOS::Xcode.latest_version}. - Xcode can be updated from - https://developer.apple.com/xcode/downloads/ - EOS - end - end - else - def check_xcode_up_to_date - if MacOS::Xcode.installed? && MacOS::Xcode.outdated? - <<-EOS.undent - Your Xcode (#{MacOS::Xcode.version}) is outdated - Please update to Xcode #{MacOS::Xcode.latest_version}. - Xcode can be updated from the App Store. - EOS - end - end - end - - def check_clt_up_to_date - if MacOS::CLT.installed? && MacOS::CLT.outdated? then <<-EOS.undent - A newer Command Line Tools release is available. - Update them from Software Update in the App Store. - EOS - end - end - elsif MacOS.version == "10.8" || MacOS.version == "10.7" - def check_for_installed_developer_tools - unless MacOS::Xcode.installed? || MacOS::CLT.installed? then <<-EOS.undent - No developer tools installed. - You should install the Command Line Tools. - The standalone package can be obtained from - https://developer.apple.com/downloads - or it can be installed via Xcode's preferences. - EOS - end - end - - def check_xcode_up_to_date - if MacOS::Xcode.installed? && MacOS::Xcode.outdated? then <<-EOS.undent - Your Xcode (#{MacOS::Xcode.version}) is outdated - Please update to Xcode #{MacOS::Xcode.latest_version}. - Xcode can be updated from - https://developer.apple.com/xcode/downloads/ - EOS - end - end - - def check_clt_up_to_date - if MacOS::CLT.installed? && MacOS::CLT.outdated? then <<-EOS.undent - A newer Command Line Tools release is available. - The standalone package can be obtained from - https://developer.apple.com/downloads - or it can be installed via Xcode's preferences. - EOS - end - end - else - def check_for_installed_developer_tools - unless MacOS::Xcode.installed? then <<-EOS.undent - Xcode is not installed. Most formulae need Xcode to build. - It can be installed from - https://developer.apple.com/xcode/downloads/ - EOS - end - end - - def check_xcode_up_to_date - if MacOS::Xcode.installed? && MacOS::Xcode.outdated? then <<-EOS.undent - Your Xcode (#{MacOS::Xcode.version}) is outdated - Please update to Xcode #{MacOS::Xcode.latest_version}. - Xcode can be updated from - https://developer.apple.com/xcode/downloads/ - EOS - end - end - end - - def check_for_osx_gcc_installer - if (MacOS.version < "10.7" || MacOS::Xcode.version > "4.1") && \ - MacOS.clang_version == "2.1" - message = <<-EOS.undent - You seem to have osx-gcc-installer installed. - Homebrew doesn't support osx-gcc-installer. It causes many builds to fail and - is an unlicensed distribution of really old Xcode files. - EOS - if MacOS.version >= :mavericks - message += <<-EOS.undent - Please run `xcode-select --install` to install the CLT. - EOS - elsif MacOS.version >= :lion - message += <<-EOS.undent - Please install the CLT or Xcode #{MacOS::Xcode.latest_version}. - EOS - else - message += <<-EOS.undent - Please install Xcode #{MacOS::Xcode.latest_version}. - EOS - end - end - end - - def check_for_stray_developer_directory - # if the uninstaller script isn't there, it's a good guess neither are - # any troublesome leftover Xcode files - uninstaller = Pathname.new("/Developer/Library/uninstall-developer-folder") - if MacOS::Xcode.version >= "4.3" && uninstaller.exist? then <<-EOS.undent - You have leftover files from an older version of Xcode. - You should delete them using: - #{uninstaller} - EOS - end - end - - def check_for_bad_install_name_tool - return if MacOS.version < "10.9" - - libs = Pathname.new("/usr/bin/install_name_tool").dynamically_linked_libraries - - # otool may not work, for example if the Xcode license hasn't been accepted yet - return if libs.empty? - - unless libs.include? "/usr/lib/libxcselect.dylib" then <<-EOS.undent - You have an outdated version of /usr/bin/install_name_tool installed. - This will cause binary package installations to fail. - This can happen if you install osx-gcc-installer or RailsInstaller. - To restore it, you must reinstall OS X or restore the binary from - the OS packages. - EOS - end - end - - def __check_subdir_access(base) - target = HOMEBREW_PREFIX+base - return unless target.exist? - - cant_read = [] - - target.find do |d| - next unless d.directory? - cant_read << d unless d.writable_real? - end - - return if cant_read.empty? - inject_file_list cant_read.sort, <<-EOS.undent - Some directories in #{target} aren't writable. - This can happen if you "sudo make install" software that isn't managed - by Homebrew. If a brew tries to add locale information to one of these - directories, then the install will fail during the link step. - - You should probably `sudo chown -R $(whoami)` them: - EOS - end - - def check_access_share_locale - __check_subdir_access "share/locale" - end - - def check_access_share_man - __check_subdir_access "share/man" - end - - def check_access_homebrew_repository - unless HOMEBREW_REPOSITORY.writable_real? then <<-EOS.undent - The #{HOMEBREW_REPOSITORY} is not writable. - - You should probably change the ownership and permissions of #{HOMEBREW_REPOSITORY} - back to your user account. - sudo chown -R $(whoami) #{HOMEBREW_REPOSITORY} - EOS - end - end - - def check_access_usr_local - return unless HOMEBREW_PREFIX.to_s == "/usr/local" - - unless HOMEBREW_PREFIX.writable_real? then <<-EOS.undent - The /usr/local directory is not writable. - Even if this directory was writable when you installed Homebrew, other - software may change permissions on this directory. Some versions of the - "InstantOn" component of Airfoil or running Cocktail cleanup/optimizations - are known to do this. - - You should probably change the ownership and permissions of /usr/local - back to your user account. - sudo chown -R $(whoami):admin /usr/local - EOS - end - end - - def check_tmpdir_sticky_bit - # Repair Disk Permissions was removed(?) in El Capitan. - # https://support.apple.com/en-us/HT201560 - if MacOS.version < "10.11" - fix_message = "Please run \"Repair Disk Permissions\" in Disk Utility." - else - fix_message = "Please execute `sudo chmod +t #{HOMEBREW_TEMP}` in your Terminal" - end - - world_writable = HOMEBREW_TEMP.stat.mode & 0777 == 0777 - if world_writable && !HOMEBREW_TEMP.sticky? then <<-EOS.undent - #{HOMEBREW_TEMP} is world-writable but does not have the sticky bit set. - #{fix_message} - EOS - end - end - - (Keg::TOP_LEVEL_DIRECTORIES + ["lib/pkgconfig"]).each do |d| - define_method("check_access_#{d.sub("/", "_")}") do - dir = HOMEBREW_PREFIX.join(d) - if dir.exist? && !dir.writable_real? then <<-EOS.undent - #{dir} isn't writable. - - This can happen if you "sudo make install" software that isn't managed - by Homebrew. If a formula tries to write a file to this directory, the - install will fail during the link step. - - You should probably change the ownership and permissions of #{dir} - back to your user account. - sudo chown -R $(whoami) #{dir} - EOS - end - end - end - - def check_access_site_packages - if Language::Python.homebrew_site_packages.exist? && !Language::Python.homebrew_site_packages.writable_real? - <<-EOS.undent - #{Language::Python.homebrew_site_packages} isn't writable. - This can happen if you "sudo pip install" software that isn't managed - by Homebrew. If you install a formula with Python modules, the install - will fail during the link step. - - You should probably change the ownership and permissions of #{Language::Python.homebrew_site_packages} - back to your user account. - sudo chown -R $(whoami) #{Language::Python.homebrew_site_packages} - EOS - end - end - - def check_access_logs - if HOMEBREW_LOGS.exist? && !HOMEBREW_LOGS.writable_real? - <<-EOS.undent - #{HOMEBREW_LOGS} isn't writable. - Homebrew writes debugging logs to this location. - - You should probably change the ownership and permissions of #{HOMEBREW_LOGS} - back to your user account. - sudo chown -R $(whoami) #{HOMEBREW_LOGS} - EOS - end - end - - def check_access_cache - if HOMEBREW_CACHE.exist? && !HOMEBREW_CACHE.writable_real? - <<-EOS.undent - #{HOMEBREW_CACHE} isn't writable. - This can happen if you run `brew install` or `brew fetch` as another user. - Homebrew caches downloaded files to this location. - - You should probably change the ownership and permissions of #{HOMEBREW_CACHE} - back to your user account. - sudo chown -R $(whoami) #{HOMEBREW_CACHE} - EOS - end - end - - def check_access_cellar - if HOMEBREW_CELLAR.exist? && !HOMEBREW_CELLAR.writable_real? - <<-EOS.undent - #{HOMEBREW_CELLAR} isn't writable. - - You should probably change the ownership and permissions of #{HOMEBREW_CELLAR} - back to your user account. - sudo chown -R $(whoami) #{HOMEBREW_CELLAR} - EOS - end - end - - def check_access_prefix_opt - opt = HOMEBREW_PREFIX.join("opt") - if opt.exist? && !opt.writable_real? - <<-EOS.undent - #{opt} isn't writable. - You should probably change the ownership and permissions of #{opt} - back to your user account. - sudo chown -R $(whoami) #{opt} - EOS - end - end - - def check_ruby_version - ruby_version = MacOS.version >= "10.9" ? "2.0" : "1.8" - if RUBY_VERSION[/\d\.\d/] != ruby_version then <<-EOS.undent - Ruby version #{RUBY_VERSION} is unsupported on #{MacOS.version}. Homebrew - is developed and tested on Ruby #{ruby_version}, and may not work correctly - on other Rubies. Patches are accepted as long as they don't cause breakage - on supported Rubies. - EOS - end - end - - def check_homebrew_prefix - unless HOMEBREW_PREFIX.to_s == "/usr/local" - <<-EOS.undent - Your Homebrew is not installed to /usr/local - You can install Homebrew anywhere you want, but some brews may only build - correctly if you install in /usr/local. Sorry! - EOS - end - end - - def check_xcode_prefix - prefix = MacOS::Xcode.prefix - return if prefix.nil? - if prefix.to_s.match(" ") - <<-EOS.undent - Xcode is installed to a directory with a space in the name. - This will cause some formulae to fail to build. - EOS - end - end - - def check_xcode_prefix_exists - prefix = MacOS::Xcode.prefix - return if prefix.nil? - unless prefix.exist? - <<-EOS.undent - The directory Xcode is reportedly installed to doesn't exist: - #{prefix} - You may need to `xcode-select` the proper path if you have moved Xcode. - EOS - end - end - - def check_xcode_select_path - if !MacOS::CLT.installed? && !File.file?("#{MacOS.active_developer_dir}/usr/bin/xcodebuild") - path = MacOS::Xcode.bundle_path - path = "/Developer" if path.nil? || !path.directory? - <<-EOS.undent - Your Xcode is configured with an invalid path. - You should change it to the correct path: - sudo xcode-select -switch #{path} - EOS - end - end - - def check_user_path_1 - $seen_prefix_bin = false - $seen_prefix_sbin = false - - out = nil - - paths.each do |p| - case p - when "/usr/bin" - unless $seen_prefix_bin - # only show the doctor message if there are any conflicts - # rationale: a default install should not trigger any brew doctor messages - conflicts = Dir["#{HOMEBREW_PREFIX}/bin/*"]. - map { |fn| File.basename fn }. - select { |bn| File.exist? "/usr/bin/#{bn}" } - - if conflicts.size > 0 - out = inject_file_list conflicts, <<-EOS.undent - /usr/bin occurs before #{HOMEBREW_PREFIX}/bin - This means that system-provided programs will be used instead of those - provided by Homebrew. The following tools exist at both paths: - EOS - - out += <<-EOS.undent - - Consider setting your PATH so that #{HOMEBREW_PREFIX}/bin - occurs before /usr/bin. Here is a one-liner: - echo 'export PATH="#{HOMEBREW_PREFIX}/bin:$PATH"' >> #{shell_profile} - EOS - end - end - when "#{HOMEBREW_PREFIX}/bin" - $seen_prefix_bin = true - when "#{HOMEBREW_PREFIX}/sbin" - $seen_prefix_sbin = true - end - end - out - end - - def check_user_path_2 - unless $seen_prefix_bin - <<-EOS.undent - Homebrew's bin was not found in your PATH. - Consider setting the PATH for example like so - echo 'export PATH="#{HOMEBREW_PREFIX}/bin:$PATH"' >> #{shell_profile} - EOS - end - end - - def check_user_path_3 - # Don't complain about sbin not being in the path if it doesn't exist - sbin = (HOMEBREW_PREFIX+"sbin") - if sbin.directory? && sbin.children.length > 0 - unless $seen_prefix_sbin - <<-EOS.undent - Homebrew's sbin was not found in your PATH but you have installed - formulae that put executables in #{HOMEBREW_PREFIX}/sbin. - Consider setting the PATH for example like so - echo 'export PATH="#{HOMEBREW_PREFIX}/sbin:$PATH"' >> #{shell_profile} - EOS - end - end - end - - def check_for_bad_curl - if MacOS.version <= "10.8" && !Formula["curl"].installed? then <<-EOS.undent - The system curl on 10.8 and below is often incapable of supporting - modern secure connections & will fail on fetching formulae. - - We recommend you: - brew install curl - EOS - end - end - - def check_user_curlrc - if %w[CURL_HOME HOME].any? { |key| ENV[key] && File.exist?("#{ENV[key]}/.curlrc") } then <<-EOS.undent - You have a curlrc file - If you have trouble downloading packages with Homebrew, then maybe this - is the problem? If the following command doesn't work, then try removing - your curlrc: - curl https://github.com - EOS - end - end - - def check_for_unsupported_curl_vars - # Support for SSL_CERT_DIR seemed to be removed in the 10.10.5 update. - if MacOS.version >= :yosemite && !ENV["SSL_CERT_DIR"].nil? then <<-EOS.undent - SSL_CERT_DIR support was removed from Apple's curl. - If fetching formulae fails you should: - unset SSL_CERT_DIR - and remove it from #{shell_profile} if present. - EOS - end - end - - def check_which_pkg_config - binary = which "pkg-config" - return if binary.nil? - - mono_config = Pathname.new("/usr/bin/pkg-config") - if mono_config.exist? && mono_config.realpath.to_s.include?("Mono.framework") then <<-EOS.undent - You have a non-Homebrew 'pkg-config' in your PATH: - /usr/bin/pkg-config => #{mono_config.realpath} - - This was most likely created by the Mono installer. `./configure` may - have problems finding brew-installed packages using this other pkg-config. - - Mono no longer installs this file as of 3.0.4. You should - `sudo rm /usr/bin/pkg-config` and upgrade to the latest version of Mono. - EOS - elsif binary.to_s != "#{HOMEBREW_PREFIX}/bin/pkg-config" then <<-EOS.undent - You have a non-Homebrew 'pkg-config' in your PATH: - #{binary} - - `./configure` may have problems finding brew-installed packages using - this other pkg-config. - 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 - f = Formulary.factory("gettext") rescue nil - return if f && f.linked_keg.directory? && @found.all? do |path| - Pathname.new(path).realpath.to_s.start_with? "#{HOMEBREW_CELLAR}/gettext" - end - - s = <<-EOS.undent_________________________________________________________72 - 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 - inject_file_list(@found, s) - end - - def check_for_iconv - unless find_relative_paths("lib/libiconv.dylib", "include/iconv.h").empty? - if (f = Formulary.factory("libiconv") rescue nil) && f.linked_keg.directory? - unless f.keg_only? then <<-EOS.undent - A libiconv formula is installed and linked - This will break stuff. For serious. Unlink it. - EOS - end - else - s = <<-EOS.undent_________________________________________________________72 - 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. OS X 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 - inject_file_list(@found, s) - end - end - end - - def check_for_config_scripts - return unless HOMEBREW_CELLAR.exist? - real_cellar = HOMEBREW_CELLAR.realpath - - scripts = [] - - whitelist = %W[ - /usr/bin /usr/sbin - /usr/X11/bin /usr/X11R6/bin /opt/X11/bin - #{HOMEBREW_PREFIX}/bin #{HOMEBREW_PREFIX}/sbin - /Applications/Server.app/Contents/ServerRoot/usr/bin - /Applications/Server.app/Contents/ServerRoot/usr/sbin - ].map(&:downcase) - - paths.each do |p| - next if whitelist.include?(p.downcase) || !File.directory?(p) - - realpath = Pathname.new(p).realpath.to_s - next if realpath.start_with?(real_cellar.to_s, HOMEBREW_CELLAR.to_s) - - scripts += Dir.chdir(p) { Dir["*-config"] }.map { |c| File.join(p, c) } - end - - return if scripts.empty? - inject_file_list scripts, <<-EOS.undent - "config" scripts exist outside your system or Homebrew directories. - `./configure` scripts often look for *-config scripts to determine if - software packages are installed, and what additional flags to use when - compiling and linking. - - Having additional scripts in your path can confuse software installed via - Homebrew if the config script overrides a system or Homebrew provided - script of the same name. We found the following "config" scripts: - EOS - end - - def check_DYLD_vars - found = ENV.keys.grep(/^DYLD_/) - return if found.empty? - s = inject_file_list found.map { |e| "#{e}: #{ENV.fetch(e)}" }, <<-EOS.undent - Setting DYLD_* vars can break dynamic linking. - Set variables: - EOS - if found.include? "DYLD_INSERT_LIBRARIES" - s += <<-EOS.undent - - Setting DYLD_INSERT_LIBRARIES can cause Go builds to fail. - Having this set is common if you use this software: - http://asepsis.binaryage.com/ - EOS - end - s - end - - def check_for_symlinked_cellar - return unless HOMEBREW_CELLAR.exist? - if HOMEBREW_CELLAR.symlink? - <<-EOS.undent - Symlinked Cellars can cause problems. - Your Homebrew Cellar is a symlink: #{HOMEBREW_CELLAR} - which resolves to: #{HOMEBREW_CELLAR.realpath} - - The recommended Homebrew installations are either: - (A) Have Cellar be a real directory inside of your HOMEBREW_PREFIX - (B) Symlink "bin/brew" into your prefix, but don't symlink "Cellar". - - Older installations of Homebrew may have created a symlinked Cellar, but this can - cause problems when two formula install to locations that are mapped on top of each - other during the linking step. - 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 - - tmp = Pathname.new(Dir.mktmpdir("doctor", HOMEBREW_TEMP)) - real_temp = tmp.realpath.parent - - where_cellar = volumes.which real_cellar - where_temp = volumes.which real_temp - - Dir.delete tmp - - unless where_cellar == where_temp then <<-EOS.undent - Your Cellar and TEMP directories are on different volumes. - OS X 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 - - def check_filesystem_case_sensitive - volumes = Volumes.new - case_sensitive_vols = [HOMEBREW_PREFIX, HOMEBREW_REPOSITORY, HOMEBREW_CELLAR, HOMEBREW_TEMP].select do |dir| - # We select the dir as being case-sensitive if either the UPCASED or the - # downcased variant is missing. - # Of course, on a case-insensitive fs, both exist because the os reports so. - # In the rare situation when the user has indeed a downcased and an upcased - # dir (e.g. /TMP and /tmp) this check falsely thinks it is case-insensitive - # but we don't care beacuse: 1. there is more than one dir checked, 2. the - # check is not vital and 3. we would have to touch files otherwise. - upcased = Pathname.new(dir.to_s.upcase) - downcased = Pathname.new(dir.to_s.downcase) - dir.exist? && !(upcased.exist? && downcased.exist?) - end.map { |case_sensitive_dir| volumes.get_mounts(case_sensitive_dir) }.uniq - return if case_sensitive_vols.empty? - <<-EOS.undent - The filesystem on #{case_sensitive_vols.join(",")} appears to be case-sensitive. - The default OS X filesystem is case-insensitive. Please report any apparent problems. - EOS - end - - def __check_git_version - # https://help.github.com/articles/https-cloning-errors - `git --version`.chomp =~ /git version ((?:\d+\.?)+)/ - - if $1 && Version.new($1) < Version.new("1.7.10") - git_upgrade_cmd = Formula["git"].any_version_installed? ? "upgrade" : "install" - - <<-EOS.undent - An outdated version of Git was detected in your PATH. - Git 1.7.10 or newer is required to perform checkouts over HTTPS from GitHub. - Please upgrade: brew #{git_upgrade_cmd} git - EOS - end - end - - def check_for_git - if Utils.git_available? - __check_git_version - else <<-EOS.undent - Git could not be found in your PATH. - Homebrew uses Git for several internal functions, and some formulae use Git - checkouts instead of stable tarballs. You may want to install Git: - brew install git - EOS - end - end - - def check_git_newline_settings - return unless Utils.git_available? - - autocrlf = `git config --get core.autocrlf`.chomp - - if autocrlf == "true" then <<-EOS.undent - Suspicious Git newline settings found. - - The detected Git newline settings will cause checkout problems: - core.autocrlf = #{autocrlf} - - If you are not routinely dealing with Windows-based projects, - consider removing these by running: - `git config --global core.autocrlf input` - EOS - end - end - - def check_git_origin - return if !Utils.git_available? || !(HOMEBREW_REPOSITORY/".git").exist? - - origin = Homebrew.git_origin - - if origin.nil? then <<-EOS.undent - Missing git origin remote. - - Without a correctly configured origin, Homebrew won't update - properly. You can solve this by adding the Homebrew remote: - cd #{HOMEBREW_REPOSITORY} - git remote add origin https://github.com/Homebrew/homebrew.git - EOS - elsif origin !~ /(mxcl|Homebrew)\/homebrew(\.git)?$/ then <<-EOS.undent - Suspicious git origin remote found. - - With a non-standard origin, Homebrew won't pull updates from - the main repository. The current git origin is: - #{origin} - - Unless you have compelling reasons, consider setting the - origin remote to point at the main repository, located at: - https://github.com/Homebrew/homebrew.git - EOS - end - end - - def check_for_autoconf - return unless MacOS::Xcode.provides_autotools? - - autoconf = which("autoconf") - safe_autoconfs = %w[/usr/bin/autoconf /Developer/usr/bin/autoconf] - unless autoconf.nil? || safe_autoconfs.include?(autoconf.to_s) then <<-EOS.undent - An "autoconf" in your path blocks the Xcode-provided version at: - #{autoconf} - - This custom autoconf may cause some Homebrew formulae to fail to compile. - EOS - end - end - - def __check_linked_brew(f) - f.installed_prefixes.each do |prefix| - prefix.find do |src| - next if src == prefix - dst = HOMEBREW_PREFIX + src.relative_path_from(prefix) - return true if dst.symlink? && src == dst.resolved_path - end - end - - false - end - - def check_for_linked_keg_only_brews - return unless HOMEBREW_CELLAR.exist? - - linked = Formula.installed.select do |f| - f.keg_only? && __check_linked_brew(f) - end - - return if linked.empty? - inject_file_list linked.map(&:full_name), <<-EOS.undent - Some keg-only formula are linked into the Cellar. - Linking a keg-only formula, such as gettext, into the cellar with - `brew link ` will cause other formulae to detect them during - the `./configure` step. This may cause problems when compiling those - other formulae. - - Binaries provided by keg-only formulae may override system binaries - with other strange results. - - You may wish to `brew unlink` these brews: - EOS - end - - def check_for_other_frameworks - # Other frameworks that are known to cause problems when present - %w[expat.framework libexpat.framework libcurl.framework]. - map { |frmwrk| "/Library/Frameworks/#{frmwrk}" }. - select { |frmwrk| File.exist? frmwrk }. - map do |frmwrk| - <<-EOS.undent - #{frmwrk} detected - This can be picked up by CMake's build system and likely cause the build to - fail. You may need to move this file out of the way to compile CMake. - EOS - end.join - end - - def check_tmpdir - tmpdir = ENV["TMPDIR"] - "TMPDIR #{tmpdir.inspect} doesn't exist." unless tmpdir.nil? || File.directory?(tmpdir) - end - - def check_missing_deps - return unless HOMEBREW_CELLAR.exist? - missing = Set.new - Homebrew.missing_deps(Formula.installed).each_value do |deps| - missing.merge(deps) - end - - if missing.any? then <<-EOS.undent - Some installed formula are missing dependencies. - You should `brew install` the missing dependencies: - - brew install #{missing.sort_by(&:full_name) * " "} - - Run `brew missing` for more details. - EOS - end - end - - def check_git_status - return unless Utils.git_available? - HOMEBREW_REPOSITORY.cd do - unless `git status --untracked-files=all --porcelain -- Library/Homebrew/ 2>/dev/null`.chomp.empty? - <<-EOS.undent_________________________________________________________72 - You have uncommitted modifications to Homebrew - If this is a surprise to you, then you should stash these modifications. - Stashing returns Homebrew to a pristine state but can be undone - should you later need to do so for some reason. - cd #{HOMEBREW_LIBRARY} && git stash && git clean -d -f - EOS - end - end - end - - def check_for_enthought_python - if which "enpkg" then <<-EOS.undent - Enthought Python was found in your PATH. - This can cause build problems, as this software installs its own - copies of iconv and libxml2 into directories that are picked up by - other build systems. - EOS - end - end - - def check_for_library_python - if File.exist?("/Library/Frameworks/Python.framework") then <<-EOS.undent - Python is installed at /Library/Frameworks/Python.framework - - Homebrew only supports building against the System-provided Python or a - brewed Python. In particular, Pythons installed to /Library can interfere - with other software installs. - EOS - end - end - - def check_for_old_homebrew_share_python_in_path - s = "" - ["", "3"].map do |suffix| - if paths.include?((HOMEBREW_PREFIX/"share/python#{suffix}").to_s) - s += "#{HOMEBREW_PREFIX}/share/python#{suffix} is not needed in PATH.\n" - end - end - unless s.empty? - s += <<-EOS.undent - Formerly homebrew put Python scripts you installed via `pip` or `pip3` - (or `easy_install`) into that directory above but now it can be removed - from your PATH variable. - Python scripts will now install into #{HOMEBREW_PREFIX}/bin. - You can delete anything, except 'Extras', from the #{HOMEBREW_PREFIX}/share/python - (and #{HOMEBREW_PREFIX}/share/python3) dir and install affected Python packages - anew with `pip install --upgrade`. - EOS - end - end - - def check_for_bad_python_symlink - return unless which "python" - `python -V 2>&1` =~ /Python (\d+)\./ - # This won't be the right warning if we matched nothing at all - return if $1.nil? - unless $1 == "2" then <<-EOS.undent - python is symlinked to python#{$1} - This will confuse build scripts and in general lead to subtle breakage. - EOS - end - end - - def check_for_non_prefixed_coreutils - gnubin = "#{Formulary.factory("coreutils").prefix}/libexec/gnubin" - if paths.include? gnubin then <<-EOS.undent - Putting non-prefixed coreutils in your path can cause gmp builds to fail. - EOS - end - end - - def check_for_non_prefixed_findutils - default_names = Tab.for_name("findutils").with? "default-names" - if default_names then <<-EOS.undent - Putting non-prefixed findutils in your path can cause python builds to fail. - EOS - end - end - - def check_for_pydistutils_cfg_in_home - if File.exist? "#{ENV["HOME"]}/.pydistutils.cfg" then <<-EOS.undent - A .pydistutils.cfg file was found in $HOME, which may cause Python - builds to fail. See: - https://bugs.python.org/issue6138 - https://bugs.python.org/issue4655 - EOS - end - end - - def check_for_outdated_homebrew - return unless Utils.git_available? - HOMEBREW_REPOSITORY.cd do - if File.directory? ".git" - local = `git rev-parse -q --verify refs/remotes/origin/master`.chomp - remote = /^([a-f0-9]{40})/.match(`git ls-remote origin refs/heads/master 2>/dev/null`) - if remote.nil? || local == remote[0] - return - end - end - - timestamp = if File.directory? ".git" - `git log -1 --format="%ct" HEAD`.to_i - else - HOMEBREW_LIBRARY.mtime.to_i - end - - if Time.now.to_i - timestamp > 60 * 60 * 24 then <<-EOS.undent - Your Homebrew is outdated. - You haven't updated for at least 24 hours. This is a long time in brewland! - To update Homebrew, run `brew update`. - EOS - end - end - end - - def check_for_unlinked_but_not_keg_only - unlinked = Formula.racks.reject do |rack| - if !(HOMEBREW_REPOSITORY/"Library/LinkedKegs"/rack.basename).directory? - begin - Formulary.from_rack(rack).keg_only? - rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError - false - end - else - true - end - end.map(&:basename) - - return if unlinked.empty? - inject_file_list unlinked, <<-EOS.undent - You have unlinked kegs in your Cellar - Leaving kegs unlinked can lead to build-trouble and cause brews that depend on - those kegs to fail to run properly once built. Run `brew link` on these: - EOS - end - - def check_xcode_license_approved - # If the user installs Xcode-only, they have to approve the - # license or no "xc*" tool will work. - if `/usr/bin/xcrun clang 2>&1` =~ /license/ && !$?.success? then <<-EOS.undent - You have not agreed to the Xcode license. - Builds will fail! Agree to the license by opening Xcode.app or running: - sudo xcodebuild -license - EOS - end - end - - def check_for_latest_xquartz - return unless MacOS::XQuartz.version - return if MacOS::XQuartz.provided_by_apple? - - installed_version = Version.new(MacOS::XQuartz.version) - latest_version = Version.new(MacOS::XQuartz.latest_version) - - return if installed_version >= latest_version - - <<-EOS.undent - Your XQuartz (#{installed_version}) is outdated - Please install XQuartz #{latest_version}: - https://xquartz.macosforge.org - EOS - end - - def check_for_old_env_vars - if ENV["HOMEBREW_KEEP_INFO"] - <<-EOS.undent - `HOMEBREW_KEEP_INFO` is no longer used - info files are no longer deleted by default; you may - remove this environment variable. - EOS - end - end - - def check_for_pth_support - homebrew_site_packages = Language::Python.homebrew_site_packages - return unless homebrew_site_packages.directory? - return if Language::Python.reads_brewed_pth_files?("python") != false - return unless Language::Python.in_sys_path?("python", homebrew_site_packages) - user_site_packages = Language::Python.user_site_packages "python" - <<-EOS.undent - Your default Python does not recognize the Homebrew site-packages - directory as a special site-packages directory, which means that .pth - files will not be followed. This means you will not be able to import - some modules after installing them with Homebrew, like wxpython. To fix - this for the current user, you can run: - - mkdir -p #{user_site_packages} - echo 'import site; site.addsitedir("#{homebrew_site_packages}")' >> #{user_site_packages}/homebrew.pth - EOS - end - - def check_for_external_cmd_name_conflict - cmds = paths.flat_map { |p| Dir["#{p}/brew-*"] }.uniq - cmds = cmds.select { |cmd| File.file?(cmd) && File.executable?(cmd) } - cmd_map = {} - cmds.each do |cmd| - cmd_name = File.basename(cmd, ".rb") - cmd_map[cmd_name] ||= [] - cmd_map[cmd_name] << cmd - end - cmd_map.reject! { |_cmd_name, cmd_paths| cmd_paths.size == 1 } - return if cmd_map.empty? - s = "You have external commands with conflicting names.\n" - cmd_map.each do |cmd_name, cmd_paths| - s += inject_file_list cmd_paths, <<-EOS.undent - - Found command `#{cmd_name}` in following places: - EOS - end - s - end - - def all - methods.map(&:to_s).grep(/^check_/) - end -end # end class Checks +require "diagnostic" module Homebrew def doctor - checks = Checks.new + checks = Diagnostic::Checks.new if ARGV.include? "--list-checks" puts checks.all.sort exit end - inject_dump_stats(checks) if ARGV.switch? "D" + checks.inject_dump_stats! if ARGV.switch? "D" if ARGV.named.empty? methods = checks.all.sort @@ -1349,19 +46,4 @@ module Homebrew puts "Your system is ready to brew." unless Homebrew.failed? end - - def inject_dump_stats(checks) - checks.extend Module.new { - def send(method, *) - time = Time.now - super - ensure - $times[method] = Time.now - time - end - } - $times = {} - at_exit do - puts $times.sort_by { |_k, v| v }.map { |k, v| "#{k}: #{v}" } - end - end end diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index dbd8f5c04e..c2ef9a3344 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -1,5 +1,5 @@ require "blacklist" -require "cmd/doctor" +require "diagnostic" require "cmd/search" require "formula_installer" require "tap" @@ -159,7 +159,7 @@ module Homebrew end def check_xcode - checks = Checks.new + checks = Diagnostic::Checks.new %w[ check_for_unsupported_osx check_for_bad_install_name_tool diff --git a/Library/Homebrew/cmd/missing.rb b/Library/Homebrew/cmd/missing.rb index 6805548a8c..3d70e5f353 100644 --- a/Library/Homebrew/cmd/missing.rb +++ b/Library/Homebrew/cmd/missing.rb @@ -1,30 +1,8 @@ require "formula" require "tab" +require "diagnostic" module Homebrew - def missing_deps(ff) - missing = {} - ff.each do |f| - missing_deps = f.recursive_dependencies do |dependent, dep| - if dep.optional? || dep.recommended? - tab = Tab.for_formula(dependent) - Dependency.prune unless tab.with?(dep) - elsif dep.build? - Dependency.prune - end - end - - missing_deps.map!(&:to_formula) - missing_deps.reject! { |d| d.installed_prefixes.any? } - - unless missing_deps.empty? - yield f.full_name, missing_deps if block_given? - missing[f.full_name] = missing_deps - end - end - missing - end - def missing return unless HOMEBREW_CELLAR.exist? @@ -34,7 +12,7 @@ module Homebrew ARGV.resolved_formulae end - missing_deps(ff) do |name, missing| + Diagnostic.missing_deps(ff) do |name, missing| print "#{name}: " if ff.size > 1 puts "#{missing * " "}" end diff --git a/Library/Homebrew/cmd/update.rb b/Library/Homebrew/cmd/update.rb index 9e2541434e..436175b259 100644 --- a/Library/Homebrew/cmd/update.rb +++ b/Library/Homebrew/cmd/update.rb @@ -1,5 +1,5 @@ require "cmd/tap" -require "cmd/doctor" +require "diagnostic" require "formula_versions" require "migrator" require "formulary" @@ -15,7 +15,7 @@ module Homebrew end # check permissions - checks = Checks.new + checks = Diagnostic::Checks.new %w[ check_access_usr_local check_access_homebrew_repository diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb new file mode 100644 index 0000000000..3dd7f566f8 --- /dev/null +++ b/Library/Homebrew/diagnostic.rb @@ -0,0 +1,1344 @@ +require "keg" +require "language/python" +require "formula" +require "version" + +module Homebrew + module Diagnostic + def self.missing_deps(ff) + missing = {} + ff.each do |f| + missing_deps = f.recursive_dependencies do |dependent, dep| + if dep.optional? || dep.recommended? + tab = Tab.for_formula(dependent) + Dependency.prune unless tab.with?(dep) + elsif dep.build? + Dependency.prune + end + end + + missing_deps.map!(&:to_formula) + missing_deps.reject! { |d| d.installed_prefixes.any? } + + unless missing_deps.empty? + yield f.full_name, missing_deps if block_given? + missing[f.full_name] = missing_deps + end + end + missing + end + + class Volumes + def initialize + @volumes = get_mounts + end + + def which(path) + vols = get_mounts path + + # no volume found + if vols.empty? + return -1 + end + + vol_index = @volumes.index(vols[0]) + # volume not found in volume list + if vol_index.nil? + return -1 + end + vol_index + end + + def get_mounts(path = nil) + vols = [] + # get the volume of path, if path is nil returns all volumes + + args = %w[/bin/df -P] + args << path if path + + Utils.popen_read(*args) do |io| + io.each_line do |line| + case line.chomp + # regex matches: /dev/disk0s2 489562928 440803616 48247312 91% / + when /^.+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]{1,3}%\s+(.+)/ + vols << $1 + end + end + end + vols + end + end + + class Checks + ############# HELPERS + # Finds files in HOMEBREW_PREFIX *and* /usr/local. + # Specify paths relative to a prefix eg. "include/foo.h". + # Sets @found for your convenience. + def find_relative_paths(*relative_paths) + @found = [HOMEBREW_PREFIX, "/usr/local"].uniq.inject([]) do |found, prefix| + found + relative_paths.map { |f| File.join(prefix, f) }.select { |f| File.exist? f } + end + end + + def inject_file_list(list, str) + list.inject(str) { |s, f| s << " #{f}\n" } + end + ############# END HELPERS + + def inject_dump_stats! + self.extend Module.new { + def send(method, *) + time = Time.now + super + ensure + $times[method] = Time.now - time + end + } + $times = {} + at_exit do + puts $times.sort_by { |_k, v| v }.map { |k, v| "#{k}: #{v}" } + end + end + + # See https://github.com/Homebrew/homebrew/pull/9986 + def check_path_for_trailing_slashes + bad_paths = ENV["PATH"].split(File::PATH_SEPARATOR).select { |p| p[-1..-1] == "/" } + return if bad_paths.empty? + inject_file_list bad_paths, <<-EOS.undent + Some directories in your path end in a slash. + Directories in your path should not end in a slash. This can break other + doctor checks. The following directories should be edited: + EOS + end + + # Installing MacGPG2 interferes with Homebrew in a big way + # https://github.com/GPGTools/MacGPG2 + def check_for_macgpg2 + return if File.exist? "/usr/local/MacGPG2/share/gnupg/VERSION" + + suspects = %w[ + /Applications/start-gpg-agent.app + /Library/Receipts/libiconv1.pkg + /usr/local/MacGPG2 + ] + + if suspects.any? { |f| File.exist? f } then <<-EOS.undent + You may have installed MacGPG2 via the package installer. + Several other checks in this script will turn up problems, such as stray + dylibs in /usr/local and permissions issues with share and man in /usr/local/. + EOS + end + end + + # Anaconda installs multiple system & brew dupes, including OpenSSL, Python, + # sqlite, libpng, Qt, etc. Regularly breaks compile on Vim, MacVim and others. + # Is flagged as part of the *-config script checks below, but people seem + # to ignore those as warnings rather than extremely likely breakage. + def check_for_anaconda + return unless which("anaconda") + return unless which("python") + + anaconda = which("anaconda").realpath.dirname + python_binary = Utils.popen_read which("python"), "-c", "import sys; sys.stdout.write(sys.executable)" + python = Pathname.new(python_binary).realpath.dirname + + # Only warn if Python lives with Anaconda, since is most problematic case. + if python == anaconda then <<-EOS.undent + Anaconda is known to frequently break Homebrew builds, including Vim and + MacVim, due to bundling many duplicates of system and Homebrew-available + tools. + + If you encounter a build failure please temporarily remove Anaconda + from your $PATH and attempt the build again prior to reporting the + failure to us. Thanks! + EOS + end + end + + def __check_stray_files(dir, pattern, white_list, message) + return unless File.directory?(dir) + + files = Dir.chdir(dir) do + Dir[pattern].select { |f| File.file?(f) && !File.symlink?(f) } - Dir.glob(white_list) + end.map { |file| File.join(dir, file) } + + inject_file_list(files, message) unless files.empty? + end + + def check_for_stray_dylibs + # Dylibs which are generally OK should be added to this list, + # with a short description of the software they come with. + white_list = [ + "libfuse.2.dylib", # MacFuse + "libfuse_ino64.2.dylib", # MacFuse + "libmacfuse_i32.2.dylib", # OSXFuse MacFuse compatibility layer + "libmacfuse_i64.2.dylib", # OSXFuse MacFuse compatibility layer + "libosxfuse_i32.2.dylib", # OSXFuse + "libosxfuse_i64.2.dylib", # OSXFuse + "libTrAPI.dylib", # TrAPI / Endpoint Security VPN + "libntfs-3g.*.dylib", # NTFS-3G + "libntfs.*.dylib", # NTFS-3G + "libublio.*.dylib", # NTFS-3G + "libUFSDNTFS.dylib", # Paragon NTFS + "libUFSDExtFS.dylib", # Paragon ExtFS + ] + + __check_stray_files "/usr/local/lib", "*.dylib", white_list, <<-EOS.undent + Unbrewed dylibs were found in /usr/local/lib. + If you didn't put them there on purpose they could cause problems when + building Homebrew formulae, and may need to be deleted. + + Unexpected dylibs: + EOS + end + + def check_for_stray_static_libs + # Static libs which are generally OK should be added to this list, + # with a short description of the software they come with. + white_list = [ + "libsecurity_agent_client.a", # OS X 10.8.2 Supplemental Update + "libsecurity_agent_server.a", # OS X 10.8.2 Supplemental Update + "libntfs-3g.a", # NTFS-3G + "libntfs.a", # NTFS-3G + "libublio.a", # NTFS-3G + ] + + __check_stray_files "/usr/local/lib", "*.a", white_list, <<-EOS.undent + Unbrewed static libraries were found in /usr/local/lib. + If you didn't put them there on purpose they could cause problems when + building Homebrew formulae, and may need to be deleted. + + Unexpected static libraries: + EOS + end + + def check_for_stray_pcs + # Package-config files which are generally OK should be added to this list, + # with a short description of the software they come with. + white_list = [ + "fuse.pc", # OSXFuse/MacFuse + "macfuse.pc", # OSXFuse MacFuse compatibility layer + "osxfuse.pc", # OSXFuse + "libntfs-3g.pc", # NTFS-3G + "libublio.pc", # NTFS-3G + ] + + __check_stray_files "/usr/local/lib/pkgconfig", "*.pc", white_list, <<-EOS.undent + Unbrewed .pc files were found in /usr/local/lib/pkgconfig. + If you didn't put them there on purpose they could cause problems when + building Homebrew formulae, and may need to be deleted. + + Unexpected .pc files: + EOS + end + + def check_for_stray_las + white_list = [ + "libfuse.la", # MacFuse + "libfuse_ino64.la", # MacFuse + "libosxfuse_i32.la", # OSXFuse + "libosxfuse_i64.la", # OSXFuse + "libntfs-3g.la", # NTFS-3G + "libntfs.la", # NTFS-3G + "libublio.la", # NTFS-3G + ] + + __check_stray_files "/usr/local/lib", "*.la", white_list, <<-EOS.undent + Unbrewed .la files were found in /usr/local/lib. + If you didn't put them there on purpose they could cause problems when + building Homebrew formulae, and may need to be deleted. + + Unexpected .la files: + EOS + end + + def check_for_stray_headers + white_list = [ + "fuse.h", # MacFuse + "fuse/**/*.h", # MacFuse + "macfuse/**/*.h", # OSXFuse MacFuse compatibility layer + "osxfuse/**/*.h", # OSXFuse + "ntfs/**/*.h", # NTFS-3G + "ntfs-3g/**/*.h", # NTFS-3G + ] + + __check_stray_files "/usr/local/include", "**/*.h", white_list, <<-EOS.undent + Unbrewed header files were found in /usr/local/include. + If you didn't put them there on purpose they could cause problems when + building Homebrew formulae, and may need to be deleted. + + Unexpected header files: + EOS + end + + def check_for_other_package_managers + ponk = MacOS.macports_or_fink + unless ponk.empty? + <<-EOS.undent + You have MacPorts or Fink installed: + #{ponk.join(", ")} + + This can cause trouble. You don't have to uninstall them, but you may want to + temporarily move them out of the way, e.g. + + sudo mv /opt/local ~/macports + EOS + end + end + + def check_for_broken_symlinks + broken_symlinks = [] + + Keg::PRUNEABLE_DIRECTORIES.each do |d| + next unless d.directory? + d.find do |path| + if path.symlink? && !path.resolved_path_exists? + broken_symlinks << path + end + end + end + return if broken_symlinks.empty? + inject_file_list broken_symlinks, <<-EOS.undent + Broken symlinks were found. Remove them with `brew prune`: + EOS + end + + def check_for_unsupported_osx + if !ARGV.homebrew_developer? && OS::Mac.prerelease? then <<-EOS.undent + You are using OS X #{MacOS.version}. + We do not provide support for this pre-release version. + You may encounter build failures or other breakages. + EOS + end + end + + # TODO: distill down into single method definition a la BuildToolsError + if MacOS.version >= "10.9" + def check_for_installed_developer_tools + unless MacOS::Xcode.installed? || MacOS::CLT.installed? then <<-EOS.undent + No developer tools installed. + Install the Command Line Tools: + xcode-select --install + EOS + end + end + + if OS::Mac.prerelease? + def check_xcode_up_to_date + if MacOS::Xcode.installed? && MacOS::Xcode.outdated? + <<-EOS.undent + Your Xcode (#{MacOS::Xcode.version}) is outdated + Please update to Xcode #{MacOS::Xcode.latest_version}. + Xcode can be updated from + https://developer.apple.com/xcode/downloads/ + EOS + end + end + else + def check_xcode_up_to_date + if MacOS::Xcode.installed? && MacOS::Xcode.outdated? + <<-EOS.undent + Your Xcode (#{MacOS::Xcode.version}) is outdated + Please update to Xcode #{MacOS::Xcode.latest_version}. + Xcode can be updated from the App Store. + EOS + end + end + end + + def check_clt_up_to_date + if MacOS::CLT.installed? && MacOS::CLT.outdated? then <<-EOS.undent + A newer Command Line Tools release is available. + Update them from Software Update in the App Store. + EOS + end + end + elsif MacOS.version == "10.8" || MacOS.version == "10.7" + def check_for_installed_developer_tools + unless MacOS::Xcode.installed? || MacOS::CLT.installed? then <<-EOS.undent + No developer tools installed. + You should install the Command Line Tools. + The standalone package can be obtained from + https://developer.apple.com/downloads + or it can be installed via Xcode's preferences. + EOS + end + end + + def check_xcode_up_to_date + if MacOS::Xcode.installed? && MacOS::Xcode.outdated? then <<-EOS.undent + Your Xcode (#{MacOS::Xcode.version}) is outdated + Please update to Xcode #{MacOS::Xcode.latest_version}. + Xcode can be updated from + https://developer.apple.com/xcode/downloads/ + EOS + end + end + + def check_clt_up_to_date + if MacOS::CLT.installed? && MacOS::CLT.outdated? then <<-EOS.undent + A newer Command Line Tools release is available. + The standalone package can be obtained from + https://developer.apple.com/downloads + or it can be installed via Xcode's preferences. + EOS + end + end + else + def check_for_installed_developer_tools + unless MacOS::Xcode.installed? then <<-EOS.undent + Xcode is not installed. Most formulae need Xcode to build. + It can be installed from + https://developer.apple.com/xcode/downloads/ + EOS + end + end + + def check_xcode_up_to_date + if MacOS::Xcode.installed? && MacOS::Xcode.outdated? then <<-EOS.undent + Your Xcode (#{MacOS::Xcode.version}) is outdated + Please update to Xcode #{MacOS::Xcode.latest_version}. + Xcode can be updated from + https://developer.apple.com/xcode/downloads/ + EOS + end + end + end + + def check_for_osx_gcc_installer + if (MacOS.version < "10.7" || MacOS::Xcode.version > "4.1") && \ + MacOS.clang_version == "2.1" + message = <<-EOS.undent + You seem to have osx-gcc-installer installed. + Homebrew doesn't support osx-gcc-installer. It causes many builds to fail and + is an unlicensed distribution of really old Xcode files. + EOS + if MacOS.version >= :mavericks + message += <<-EOS.undent + Please run `xcode-select --install` to install the CLT. + EOS + elsif MacOS.version >= :lion + message += <<-EOS.undent + Please install the CLT or Xcode #{MacOS::Xcode.latest_version}. + EOS + else + message += <<-EOS.undent + Please install Xcode #{MacOS::Xcode.latest_version}. + EOS + end + end + end + + def check_for_stray_developer_directory + # if the uninstaller script isn't there, it's a good guess neither are + # any troublesome leftover Xcode files + uninstaller = Pathname.new("/Developer/Library/uninstall-developer-folder") + if MacOS::Xcode.version >= "4.3" && uninstaller.exist? then <<-EOS.undent + You have leftover files from an older version of Xcode. + You should delete them using: + #{uninstaller} + EOS + end + end + + def check_for_bad_install_name_tool + return if MacOS.version < "10.9" + + libs = Pathname.new("/usr/bin/install_name_tool").dynamically_linked_libraries + + # otool may not work, for example if the Xcode license hasn't been accepted yet + return if libs.empty? + + unless libs.include? "/usr/lib/libxcselect.dylib" then <<-EOS.undent + You have an outdated version of /usr/bin/install_name_tool installed. + This will cause binary package installations to fail. + This can happen if you install osx-gcc-installer or RailsInstaller. + To restore it, you must reinstall OS X or restore the binary from + the OS packages. + EOS + end + end + + def __check_subdir_access(base) + target = HOMEBREW_PREFIX+base + return unless target.exist? + + cant_read = [] + + target.find do |d| + next unless d.directory? + cant_read << d unless d.writable_real? + end + + return if cant_read.empty? + inject_file_list cant_read.sort, <<-EOS.undent + Some directories in #{target} aren't writable. + This can happen if you "sudo make install" software that isn't managed + by Homebrew. If a brew tries to add locale information to one of these + directories, then the install will fail during the link step. + + You should probably `sudo chown -R $(whoami)` them: + EOS + end + + def check_access_share_locale + __check_subdir_access "share/locale" + end + + def check_access_share_man + __check_subdir_access "share/man" + end + + def check_access_homebrew_repository + unless HOMEBREW_REPOSITORY.writable_real? then <<-EOS.undent + The #{HOMEBREW_REPOSITORY} is not writable. + + You should probably change the ownership and permissions of #{HOMEBREW_REPOSITORY} + back to your user account. + sudo chown -R $(whoami) #{HOMEBREW_REPOSITORY} + EOS + end + end + + def check_access_usr_local + return unless HOMEBREW_PREFIX.to_s == "/usr/local" + + unless HOMEBREW_PREFIX.writable_real? then <<-EOS.undent + The /usr/local directory is not writable. + Even if this directory was writable when you installed Homebrew, other + software may change permissions on this directory. Some versions of the + "InstantOn" component of Airfoil or running Cocktail cleanup/optimizations + are known to do this. + + You should probably change the ownership and permissions of /usr/local + back to your user account. + sudo chown -R $(whoami):admin /usr/local + EOS + end + end + + def check_tmpdir_sticky_bit + # Repair Disk Permissions was removed(?) in El Capitan. + # https://support.apple.com/en-us/HT201560 + if MacOS.version < "10.11" + fix_message = "Please run \"Repair Disk Permissions\" in Disk Utility." + else + fix_message = "Please execute `sudo chmod +t #{HOMEBREW_TEMP}` in your Terminal" + end + + world_writable = HOMEBREW_TEMP.stat.mode & 0777 == 0777 + if world_writable && !HOMEBREW_TEMP.sticky? then <<-EOS.undent + #{HOMEBREW_TEMP} is world-writable but does not have the sticky bit set. + #{fix_message} + EOS + end + end + + (Keg::TOP_LEVEL_DIRECTORIES + ["lib/pkgconfig"]).each do |d| + define_method("check_access_#{d.sub("/", "_")}") do + dir = HOMEBREW_PREFIX.join(d) + if dir.exist? && !dir.writable_real? then <<-EOS.undent + #{dir} isn't writable. + + This can happen if you "sudo make install" software that isn't managed + by Homebrew. If a formula tries to write a file to this directory, the + install will fail during the link step. + + You should probably change the ownership and permissions of #{dir} + back to your user account. + sudo chown -R $(whoami) #{dir} + EOS + end + end + end + + def check_access_site_packages + if Language::Python.homebrew_site_packages.exist? && !Language::Python.homebrew_site_packages.writable_real? + <<-EOS.undent + #{Language::Python.homebrew_site_packages} isn't writable. + This can happen if you "sudo pip install" software that isn't managed + by Homebrew. If you install a formula with Python modules, the install + will fail during the link step. + + You should probably change the ownership and permissions of #{Language::Python.homebrew_site_packages} + back to your user account. + sudo chown -R $(whoami) #{Language::Python.homebrew_site_packages} + EOS + end + end + + def check_access_logs + if HOMEBREW_LOGS.exist? && !HOMEBREW_LOGS.writable_real? + <<-EOS.undent + #{HOMEBREW_LOGS} isn't writable. + Homebrew writes debugging logs to this location. + + You should probably change the ownership and permissions of #{HOMEBREW_LOGS} + back to your user account. + sudo chown -R $(whoami) #{HOMEBREW_LOGS} + EOS + end + end + + def check_access_cache + if HOMEBREW_CACHE.exist? && !HOMEBREW_CACHE.writable_real? + <<-EOS.undent + #{HOMEBREW_CACHE} isn't writable. + This can happen if you run `brew install` or `brew fetch` as another user. + Homebrew caches downloaded files to this location. + + You should probably change the ownership and permissions of #{HOMEBREW_CACHE} + back to your user account. + sudo chown -R $(whoami) #{HOMEBREW_CACHE} + EOS + end + end + + def check_access_cellar + if HOMEBREW_CELLAR.exist? && !HOMEBREW_CELLAR.writable_real? + <<-EOS.undent + #{HOMEBREW_CELLAR} isn't writable. + + You should probably change the ownership and permissions of #{HOMEBREW_CELLAR} + back to your user account. + sudo chown -R $(whoami) #{HOMEBREW_CELLAR} + EOS + end + end + + def check_access_prefix_opt + opt = HOMEBREW_PREFIX.join("opt") + if opt.exist? && !opt.writable_real? + <<-EOS.undent + #{opt} isn't writable. + You should probably change the ownership and permissions of #{opt} + back to your user account. + sudo chown -R $(whoami) #{opt} + EOS + end + end + + def check_ruby_version + ruby_version = MacOS.version >= "10.9" ? "2.0" : "1.8" + if RUBY_VERSION[/\d\.\d/] != ruby_version then <<-EOS.undent + Ruby version #{RUBY_VERSION} is unsupported on #{MacOS.version}. Homebrew + is developed and tested on Ruby #{ruby_version}, and may not work correctly + on other Rubies. Patches are accepted as long as they don't cause breakage + on supported Rubies. + EOS + end + end + + def check_homebrew_prefix + unless HOMEBREW_PREFIX.to_s == "/usr/local" + <<-EOS.undent + Your Homebrew is not installed to /usr/local + You can install Homebrew anywhere you want, but some brews may only build + correctly if you install in /usr/local. Sorry! + EOS + end + end + + def check_xcode_prefix + prefix = MacOS::Xcode.prefix + return if prefix.nil? + if prefix.to_s.match(" ") + <<-EOS.undent + Xcode is installed to a directory with a space in the name. + This will cause some formulae to fail to build. + EOS + end + end + + def check_xcode_prefix_exists + prefix = MacOS::Xcode.prefix + return if prefix.nil? + unless prefix.exist? + <<-EOS.undent + The directory Xcode is reportedly installed to doesn't exist: + #{prefix} + You may need to `xcode-select` the proper path if you have moved Xcode. + EOS + end + end + + def check_xcode_select_path + if !MacOS::CLT.installed? && !File.file?("#{MacOS.active_developer_dir}/usr/bin/xcodebuild") + path = MacOS::Xcode.bundle_path + path = "/Developer" if path.nil? || !path.directory? + <<-EOS.undent + Your Xcode is configured with an invalid path. + You should change it to the correct path: + sudo xcode-select -switch #{path} + EOS + end + end + + def check_user_path_1 + $seen_prefix_bin = false + $seen_prefix_sbin = false + + out = nil + + paths.each do |p| + case p + when "/usr/bin" + unless $seen_prefix_bin + # only show the doctor message if there are any conflicts + # rationale: a default install should not trigger any brew doctor messages + conflicts = Dir["#{HOMEBREW_PREFIX}/bin/*"]. + map { |fn| File.basename fn }. + select { |bn| File.exist? "/usr/bin/#{bn}" } + + if conflicts.size > 0 + out = inject_file_list conflicts, <<-EOS.undent + /usr/bin occurs before #{HOMEBREW_PREFIX}/bin + This means that system-provided programs will be used instead of those + provided by Homebrew. The following tools exist at both paths: + EOS + + out += <<-EOS.undent + + Consider setting your PATH so that #{HOMEBREW_PREFIX}/bin + occurs before /usr/bin. Here is a one-liner: + echo 'export PATH="#{HOMEBREW_PREFIX}/bin:$PATH"' >> #{shell_profile} + EOS + end + end + when "#{HOMEBREW_PREFIX}/bin" + $seen_prefix_bin = true + when "#{HOMEBREW_PREFIX}/sbin" + $seen_prefix_sbin = true + end + end + out + end + + def check_user_path_2 + unless $seen_prefix_bin + <<-EOS.undent + Homebrew's bin was not found in your PATH. + Consider setting the PATH for example like so + echo 'export PATH="#{HOMEBREW_PREFIX}/bin:$PATH"' >> #{shell_profile} + EOS + end + end + + def check_user_path_3 + # Don't complain about sbin not being in the path if it doesn't exist + sbin = (HOMEBREW_PREFIX+"sbin") + if sbin.directory? && sbin.children.length > 0 + unless $seen_prefix_sbin + <<-EOS.undent + Homebrew's sbin was not found in your PATH but you have installed + formulae that put executables in #{HOMEBREW_PREFIX}/sbin. + Consider setting the PATH for example like so + echo 'export PATH="#{HOMEBREW_PREFIX}/sbin:$PATH"' >> #{shell_profile} + EOS + end + end + end + + def check_for_bad_curl + if MacOS.version <= "10.8" && !Formula["curl"].installed? then <<-EOS.undent + The system curl on 10.8 and below is often incapable of supporting + modern secure connections & will fail on fetching formulae. + + We recommend you: + brew install curl + EOS + end + end + + def check_user_curlrc + if %w[CURL_HOME HOME].any? { |key| ENV[key] && File.exist?("#{ENV[key]}/.curlrc") } then <<-EOS.undent + You have a curlrc file + If you have trouble downloading packages with Homebrew, then maybe this + is the problem? If the following command doesn't work, then try removing + your curlrc: + curl https://github.com + EOS + end + end + + def check_for_unsupported_curl_vars + # Support for SSL_CERT_DIR seemed to be removed in the 10.10.5 update. + if MacOS.version >= :yosemite && !ENV["SSL_CERT_DIR"].nil? then <<-EOS.undent + SSL_CERT_DIR support was removed from Apple's curl. + If fetching formulae fails you should: + unset SSL_CERT_DIR + and remove it from #{shell_profile} if present. + EOS + end + end + + def check_which_pkg_config + binary = which "pkg-config" + return if binary.nil? + + mono_config = Pathname.new("/usr/bin/pkg-config") + if mono_config.exist? && mono_config.realpath.to_s.include?("Mono.framework") then <<-EOS.undent + You have a non-Homebrew 'pkg-config' in your PATH: + /usr/bin/pkg-config => #{mono_config.realpath} + + This was most likely created by the Mono installer. `./configure` may + have problems finding brew-installed packages using this other pkg-config. + + Mono no longer installs this file as of 3.0.4. You should + `sudo rm /usr/bin/pkg-config` and upgrade to the latest version of Mono. + EOS + elsif binary.to_s != "#{HOMEBREW_PREFIX}/bin/pkg-config" then <<-EOS.undent + You have a non-Homebrew 'pkg-config' in your PATH: + #{binary} + + `./configure` may have problems finding brew-installed packages using + this other pkg-config. + 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 + f = Formulary.factory("gettext") rescue nil + return if f && f.linked_keg.directory? && @found.all? do |path| + Pathname.new(path).realpath.to_s.start_with? "#{HOMEBREW_CELLAR}/gettext" + end + + s = <<-EOS.undent_________________________________________________________72 + 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 + inject_file_list(@found, s) + end + + def check_for_iconv + unless find_relative_paths("lib/libiconv.dylib", "include/iconv.h").empty? + if (f = Formulary.factory("libiconv") rescue nil) && f.linked_keg.directory? + unless f.keg_only? then <<-EOS.undent + A libiconv formula is installed and linked + This will break stuff. For serious. Unlink it. + EOS + end + else + s = <<-EOS.undent_________________________________________________________72 + 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. OS X 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 + inject_file_list(@found, s) + end + end + end + + def check_for_config_scripts + return unless HOMEBREW_CELLAR.exist? + real_cellar = HOMEBREW_CELLAR.realpath + + scripts = [] + + whitelist = %W[ + /usr/bin /usr/sbin + /usr/X11/bin /usr/X11R6/bin /opt/X11/bin + #{HOMEBREW_PREFIX}/bin #{HOMEBREW_PREFIX}/sbin + /Applications/Server.app/Contents/ServerRoot/usr/bin + /Applications/Server.app/Contents/ServerRoot/usr/sbin + ].map(&:downcase) + + paths.each do |p| + next if whitelist.include?(p.downcase) || !File.directory?(p) + + realpath = Pathname.new(p).realpath.to_s + next if realpath.start_with?(real_cellar.to_s, HOMEBREW_CELLAR.to_s) + + scripts += Dir.chdir(p) { Dir["*-config"] }.map { |c| File.join(p, c) } + end + + return if scripts.empty? + inject_file_list scripts, <<-EOS.undent + "config" scripts exist outside your system or Homebrew directories. + `./configure` scripts often look for *-config scripts to determine if + software packages are installed, and what additional flags to use when + compiling and linking. + + Having additional scripts in your path can confuse software installed via + Homebrew if the config script overrides a system or Homebrew provided + script of the same name. We found the following "config" scripts: + EOS + end + + def check_DYLD_vars + found = ENV.keys.grep(/^DYLD_/) + return if found.empty? + s = inject_file_list found.map { |e| "#{e}: #{ENV.fetch(e)}" }, <<-EOS.undent + Setting DYLD_* vars can break dynamic linking. + Set variables: + EOS + if found.include? "DYLD_INSERT_LIBRARIES" + s += <<-EOS.undent + + Setting DYLD_INSERT_LIBRARIES can cause Go builds to fail. + Having this set is common if you use this software: + http://asepsis.binaryage.com/ + EOS + end + s + end + + def check_for_symlinked_cellar + return unless HOMEBREW_CELLAR.exist? + if HOMEBREW_CELLAR.symlink? + <<-EOS.undent + Symlinked Cellars can cause problems. + Your Homebrew Cellar is a symlink: #{HOMEBREW_CELLAR} + which resolves to: #{HOMEBREW_CELLAR.realpath} + + The recommended Homebrew installations are either: + (A) Have Cellar be a real directory inside of your HOMEBREW_PREFIX + (B) Symlink "bin/brew" into your prefix, but don't symlink "Cellar". + + Older installations of Homebrew may have created a symlinked Cellar, but this can + cause problems when two formula install to locations that are mapped on top of each + other during the linking step. + 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 + + tmp = Pathname.new(Dir.mktmpdir("doctor", HOMEBREW_TEMP)) + real_temp = tmp.realpath.parent + + where_cellar = volumes.which real_cellar + where_temp = volumes.which real_temp + + Dir.delete tmp + + unless where_cellar == where_temp then <<-EOS.undent + Your Cellar and TEMP directories are on different volumes. + OS X 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 + + def check_filesystem_case_sensitive + volumes = Volumes.new + case_sensitive_vols = [HOMEBREW_PREFIX, HOMEBREW_REPOSITORY, HOMEBREW_CELLAR, HOMEBREW_TEMP].select do |dir| + # We select the dir as being case-sensitive if either the UPCASED or the + # downcased variant is missing. + # Of course, on a case-insensitive fs, both exist because the os reports so. + # In the rare situation when the user has indeed a downcased and an upcased + # dir (e.g. /TMP and /tmp) this check falsely thinks it is case-insensitive + # but we don't care beacuse: 1. there is more than one dir checked, 2. the + # check is not vital and 3. we would have to touch files otherwise. + upcased = Pathname.new(dir.to_s.upcase) + downcased = Pathname.new(dir.to_s.downcase) + dir.exist? && !(upcased.exist? && downcased.exist?) + end.map { |case_sensitive_dir| volumes.get_mounts(case_sensitive_dir) }.uniq + return if case_sensitive_vols.empty? + <<-EOS.undent + The filesystem on #{case_sensitive_vols.join(",")} appears to be case-sensitive. + The default OS X filesystem is case-insensitive. Please report any apparent problems. + EOS + end + + def __check_git_version + # https://help.github.com/articles/https-cloning-errors + `git --version`.chomp =~ /git version ((?:\d+\.?)+)/ + + if $1 && Version.new($1) < Version.new("1.7.10") + git_upgrade_cmd = Formula["git"].any_version_installed? ? "upgrade" : "install" + + <<-EOS.undent + An outdated version of Git was detected in your PATH. + Git 1.7.10 or newer is required to perform checkouts over HTTPS from GitHub. + Please upgrade: brew #{git_upgrade_cmd} git + EOS + end + end + + def check_for_git + if Utils.git_available? + __check_git_version + else <<-EOS.undent + Git could not be found in your PATH. + Homebrew uses Git for several internal functions, and some formulae use Git + checkouts instead of stable tarballs. You may want to install Git: + brew install git + EOS + end + end + + def check_git_newline_settings + return unless Utils.git_available? + + autocrlf = `git config --get core.autocrlf`.chomp + + if autocrlf == "true" then <<-EOS.undent + Suspicious Git newline settings found. + + The detected Git newline settings will cause checkout problems: + core.autocrlf = #{autocrlf} + + If you are not routinely dealing with Windows-based projects, + consider removing these by running: + `git config --global core.autocrlf input` + EOS + end + end + + def check_git_origin + return if !Utils.git_available? || !(HOMEBREW_REPOSITORY/".git").exist? + + origin = Homebrew.git_origin + + if origin.nil? then <<-EOS.undent + Missing git origin remote. + + Without a correctly configured origin, Homebrew won't update + properly. You can solve this by adding the Homebrew remote: + cd #{HOMEBREW_REPOSITORY} + git remote add origin https://github.com/Homebrew/homebrew.git + EOS + elsif origin !~ /(mxcl|Homebrew)\/homebrew(\.git)?$/ then <<-EOS.undent + Suspicious git origin remote found. + + With a non-standard origin, Homebrew won't pull updates from + the main repository. The current git origin is: + #{origin} + + Unless you have compelling reasons, consider setting the + origin remote to point at the main repository, located at: + https://github.com/Homebrew/homebrew.git + EOS + end + end + + def check_for_autoconf + return unless MacOS::Xcode.provides_autotools? + + autoconf = which("autoconf") + safe_autoconfs = %w[/usr/bin/autoconf /Developer/usr/bin/autoconf] + unless autoconf.nil? || safe_autoconfs.include?(autoconf.to_s) then <<-EOS.undent + An "autoconf" in your path blocks the Xcode-provided version at: + #{autoconf} + + This custom autoconf may cause some Homebrew formulae to fail to compile. + EOS + end + end + + def __check_linked_brew(f) + f.installed_prefixes.each do |prefix| + prefix.find do |src| + next if src == prefix + dst = HOMEBREW_PREFIX + src.relative_path_from(prefix) + return true if dst.symlink? && src == dst.resolved_path + end + end + + false + end + + def check_for_linked_keg_only_brews + return unless HOMEBREW_CELLAR.exist? + + linked = Formula.installed.select do |f| + f.keg_only? && __check_linked_brew(f) + end + + return if linked.empty? + inject_file_list linked.map(&:full_name), <<-EOS.undent + Some keg-only formula are linked into the Cellar. + Linking a keg-only formula, such as gettext, into the cellar with + `brew link ` will cause other formulae to detect them during + the `./configure` step. This may cause problems when compiling those + other formulae. + + Binaries provided by keg-only formulae may override system binaries + with other strange results. + + You may wish to `brew unlink` these brews: + EOS + end + + def check_for_other_frameworks + # Other frameworks that are known to cause problems when present + %w[expat.framework libexpat.framework libcurl.framework]. + map { |frmwrk| "/Library/Frameworks/#{frmwrk}" }. + select { |frmwrk| File.exist? frmwrk }. + map do |frmwrk| + <<-EOS.undent + #{frmwrk} detected + This can be picked up by CMake's build system and likely cause the build to + fail. You may need to move this file out of the way to compile CMake. + EOS + end.join + end + + def check_tmpdir + tmpdir = ENV["TMPDIR"] + "TMPDIR #{tmpdir.inspect} doesn't exist." unless tmpdir.nil? || File.directory?(tmpdir) + end + + def check_missing_deps + return unless HOMEBREW_CELLAR.exist? + missing = Set.new + Homebrew::Diagnostic.missing_deps(Formula.installed).each_value do |deps| + missing.merge(deps) + end + + if missing.any? then <<-EOS.undent + Some installed formula are missing dependencies. + You should `brew install` the missing dependencies: + + brew install #{missing.sort_by(&:full_name) * " "} + + Run `brew missing` for more details. + EOS + end + end + + def check_git_status + return unless Utils.git_available? + HOMEBREW_REPOSITORY.cd do + unless `git status --untracked-files=all --porcelain -- Library/Homebrew/ 2>/dev/null`.chomp.empty? + <<-EOS.undent_________________________________________________________72 + You have uncommitted modifications to Homebrew + If this is a surprise to you, then you should stash these modifications. + Stashing returns Homebrew to a pristine state but can be undone + should you later need to do so for some reason. + cd #{HOMEBREW_LIBRARY} && git stash && git clean -d -f + EOS + end + end + end + + def check_for_enthought_python + if which "enpkg" then <<-EOS.undent + Enthought Python was found in your PATH. + This can cause build problems, as this software installs its own + copies of iconv and libxml2 into directories that are picked up by + other build systems. + EOS + end + end + + def check_for_library_python + if File.exist?("/Library/Frameworks/Python.framework") then <<-EOS.undent + Python is installed at /Library/Frameworks/Python.framework + + Homebrew only supports building against the System-provided Python or a + brewed Python. In particular, Pythons installed to /Library can interfere + with other software installs. + EOS + end + end + + def check_for_old_homebrew_share_python_in_path + s = "" + ["", "3"].map do |suffix| + if paths.include?((HOMEBREW_PREFIX/"share/python#{suffix}").to_s) + s += "#{HOMEBREW_PREFIX}/share/python#{suffix} is not needed in PATH.\n" + end + end + unless s.empty? + s += <<-EOS.undent + Formerly homebrew put Python scripts you installed via `pip` or `pip3` + (or `easy_install`) into that directory above but now it can be removed + from your PATH variable. + Python scripts will now install into #{HOMEBREW_PREFIX}/bin. + You can delete anything, except 'Extras', from the #{HOMEBREW_PREFIX}/share/python + (and #{HOMEBREW_PREFIX}/share/python3) dir and install affected Python packages + anew with `pip install --upgrade`. + EOS + end + end + + def check_for_bad_python_symlink + return unless which "python" + `python -V 2>&1` =~ /Python (\d+)\./ + # This won't be the right warning if we matched nothing at all + return if $1.nil? + unless $1 == "2" then <<-EOS.undent + python is symlinked to python#{$1} + This will confuse build scripts and in general lead to subtle breakage. + EOS + end + end + + def check_for_non_prefixed_coreutils + gnubin = "#{Formulary.factory("coreutils").prefix}/libexec/gnubin" + if paths.include? gnubin then <<-EOS.undent + Putting non-prefixed coreutils in your path can cause gmp builds to fail. + EOS + end + end + + def check_for_non_prefixed_findutils + default_names = Tab.for_name("findutils").with? "default-names" + if default_names then <<-EOS.undent + Putting non-prefixed findutils in your path can cause python builds to fail. + EOS + end + end + + def check_for_pydistutils_cfg_in_home + if File.exist? "#{ENV["HOME"]}/.pydistutils.cfg" then <<-EOS.undent + A .pydistutils.cfg file was found in $HOME, which may cause Python + builds to fail. See: + https://bugs.python.org/issue6138 + https://bugs.python.org/issue4655 + EOS + end + end + + def check_for_outdated_homebrew + return unless Utils.git_available? + HOMEBREW_REPOSITORY.cd do + if File.directory? ".git" + local = `git rev-parse -q --verify refs/remotes/origin/master`.chomp + remote = /^([a-f0-9]{40})/.match(`git ls-remote origin refs/heads/master 2>/dev/null`) + if remote.nil? || local == remote[0] + return + end + end + + timestamp = if File.directory? ".git" + `git log -1 --format="%ct" HEAD`.to_i + else + HOMEBREW_LIBRARY.mtime.to_i + end + + if Time.now.to_i - timestamp > 60 * 60 * 24 then <<-EOS.undent + Your Homebrew is outdated. + You haven't updated for at least 24 hours. This is a long time in brewland! + To update Homebrew, run `brew update`. + EOS + end + end + end + + def check_for_unlinked_but_not_keg_only + unlinked = Formula.racks.reject do |rack| + if !(HOMEBREW_REPOSITORY/"Library/LinkedKegs"/rack.basename).directory? + begin + Formulary.from_rack(rack).keg_only? + rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + false + end + else + true + end + end.map(&:basename) + + return if unlinked.empty? + inject_file_list unlinked, <<-EOS.undent + You have unlinked kegs in your Cellar + Leaving kegs unlinked can lead to build-trouble and cause brews that depend on + those kegs to fail to run properly once built. Run `brew link` on these: + EOS + end + + def check_xcode_license_approved + # If the user installs Xcode-only, they have to approve the + # license or no "xc*" tool will work. + if `/usr/bin/xcrun clang 2>&1` =~ /license/ && !$?.success? then <<-EOS.undent + You have not agreed to the Xcode license. + Builds will fail! Agree to the license by opening Xcode.app or running: + sudo xcodebuild -license + EOS + end + end + + def check_for_latest_xquartz + return unless MacOS::XQuartz.version + return if MacOS::XQuartz.provided_by_apple? + + installed_version = Version.new(MacOS::XQuartz.version) + latest_version = Version.new(MacOS::XQuartz.latest_version) + + return if installed_version >= latest_version + + <<-EOS.undent + Your XQuartz (#{installed_version}) is outdated + Please install XQuartz #{latest_version}: + https://xquartz.macosforge.org + EOS + end + + def check_for_old_env_vars + if ENV["HOMEBREW_KEEP_INFO"] + <<-EOS.undent + `HOMEBREW_KEEP_INFO` is no longer used + info files are no longer deleted by default; you may + remove this environment variable. + EOS + end + end + + def check_for_pth_support + homebrew_site_packages = Language::Python.homebrew_site_packages + return unless homebrew_site_packages.directory? + return if Language::Python.reads_brewed_pth_files?("python") != false + return unless Language::Python.in_sys_path?("python", homebrew_site_packages) + user_site_packages = Language::Python.user_site_packages "python" + <<-EOS.undent + Your default Python does not recognize the Homebrew site-packages + directory as a special site-packages directory, which means that .pth + files will not be followed. This means you will not be able to import + some modules after installing them with Homebrew, like wxpython. To fix + this for the current user, you can run: + + mkdir -p #{user_site_packages} + echo 'import site; site.addsitedir("#{homebrew_site_packages}")' >> #{user_site_packages}/homebrew.pth + EOS + end + + def check_for_external_cmd_name_conflict + cmds = paths.flat_map { |p| Dir["#{p}/brew-*"] }.uniq + cmds = cmds.select { |cmd| File.file?(cmd) && File.executable?(cmd) } + cmd_map = {} + cmds.each do |cmd| + cmd_name = File.basename(cmd, ".rb") + cmd_map[cmd_name] ||= [] + cmd_map[cmd_name] << cmd + end + cmd_map.reject! { |_cmd_name, cmd_paths| cmd_paths.size == 1 } + return if cmd_map.empty? + s = "You have external commands with conflicting names.\n" + cmd_map.each do |cmd_name, cmd_paths| + s += inject_file_list cmd_paths, <<-EOS.undent + + Found command `#{cmd_name}` in following places: + EOS + end + s + end + + def all + methods.map(&:to_s).grep(/^check_/) + end + end # end class Checks + end +end diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index ad4e600f63..7e6103a390 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -265,8 +265,8 @@ class BuildError < RuntimeError puts issues.map { |i| "#{i["title"]} #{i["html_url"]}" }.join("\n") end - require "cmd/doctor" - unsupported_osx = Checks.new.check_for_unsupported_osx + require "diagnostic" + unsupported_osx = Diagnostic::Checks.new.check_for_unsupported_osx opoo unsupported_osx if unsupported_osx end end diff --git a/Library/Homebrew/test/test_integration_cmds.rb b/Library/Homebrew/test/test_integration_cmds.rb index 41cc9ac050..64ce9f76ab 100644 --- a/Library/Homebrew/test/test_integration_cmds.rb +++ b/Library/Homebrew/test/test_integration_cmds.rb @@ -1,6 +1,7 @@ require "bundler" require "testing_env" require "core_formula_repository" +require "fileutils" class IntegrationCommandTests < Homebrew::TestCase def cmd_output(*args) @@ -152,7 +153,7 @@ class IntegrationCommandTests < Homebrew::TestCase cmd("readall", "--aliases", "--syntax") cmd("readall", "Homebrew/homebrew") ensure - formula_file.unlink + formula_file.unlink unless formula_file.nil? repo.alias_dir.rmtree end @@ -181,4 +182,58 @@ class IntegrationCommandTests < Homebrew::TestCase ensure Tap::TAP_DIRECTORY.rmtree end + + def test_missing + url = "file://#{File.expand_path("..", __FILE__)}/tarballs/testball-0.1.tbz" + sha256 = "1dfb13ce0f6143fe675b525fc9e168adb2215c5d5965c9f57306bb993170914f" + repo = CoreFormulaRepository.new + foo_file = repo.formula_dir/"foo.rb" + foo_file.write <<-EOS.undent + class Foo < Formula + url "#{url}" + sha256 "#{sha256}" + end + EOS + + bar_file = repo.formula_dir/"bar.rb" + bar_file.write <<-EOS.undent + class Bar < Formula + url "#{url}" + sha256 "#{sha256}" + depends_on "foo" + end + EOS + + cmd("install", "bar") + cmd("uninstall", "foo") + assert_match "foo", cmd("missing") + ensure + cmd("uninstall", "--force", "foo", "bar") + cmd("cleanup", "--force", "--prune=all") + foo_file.unlink unless foo_file.nil? + bar_file.unlink unless bar_file.nil? + end + + def test_doctor_check_path_for_trailing_slashes + assert_match "Some directories in your path end in a slash", + cmd_fail("doctor", "check_path_for_trailing_slashes", + {"PATH" => ENV["PATH"] + File::PATH_SEPARATOR + "/foo/bar/"}) + end + + def test_doctor_check_for_anaconda + mktmpdir do |path| + anaconda = "#{path}/anaconda" + python = "#{path}/python" + FileUtils.touch anaconda + File.open(python, "w") do |file| + file.write("#! #{`which bash`}\necho -n '#{python}'\n") + end + FileUtils.chmod 0777, anaconda + FileUtils.chmod 0777, python + + assert_match "Anaconda", + cmd_fail("doctor", "check_for_anaconda", + {"PATH" => path + File::PATH_SEPARATOR + ENV["PATH"]}) + end + end end