check in a somewhat stripped fork of Homebrew

under lib/homebrew-fork
This commit is contained in:
Roland Walker 2014-12-13 13:07:12 -05:00
parent cd720285e1
commit 9868944de5
83 changed files with 10299 additions and 0 deletions

View File

@ -0,0 +1,22 @@
Copyright 2009-2014 Max Howell and other contributors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,99 @@
def blacklisted? name
case name.downcase
when 'screen', /^rubygems?$/ then <<-EOS.undent
Apple distributes #{name} with OS X, you can find it in /usr/bin.
EOS
when 'libpcap' then <<-EOS.undent
Apple distributes #{name} with OS X, you can find it in /usr/lib.
EOS
when 'libiconv' then <<-EOS.undent
Apple distributes #{name} with OS X, you can find it in /usr/lib.
Some build scripts fail to detect it correctly, please check existing
formulae for solutions.
EOS
when 'tex', 'tex-live', 'texlive', 'latex' then <<-EOS.undent
Installing TeX from source is weird and gross, requires a lot of patches,
and only builds 32-bit (and thus can't use Homebrew deps on Snow Leopard.)
We recommend using a MacTeX distribution: http://www.tug.org/mactex/
EOS
when 'pip' then <<-EOS.undent
Homebrew provides pip via: `brew install python`. However you will then
have two Pythons installed on your Mac, so alternatively you can:
sudo easy_install pip
EOS
when 'pil' then <<-EOS.undent
Instead of PIL, consider `pip install pillow` or `brew install Homebrew/python/pillow`.
EOS
when 'macruby' then <<-EOS.undent
MacRuby works better when you install their package:
http://www.macruby.org/
EOS
when /(lib)?lzma/
"lzma is now part of the xz formula."
when 'xcode'
if MacOS.version >= :lion
<<-EOS.undent
Xcode can be installed from the App Store.
EOS
else
<<-EOS.undent
Xcode can be installed from https://developer.apple.com/downloads/
EOS
end
when 'gtest', 'googletest', 'google-test' then <<-EOS.undent
Installing gtest system-wide is not recommended; it should be vendored
in your projects that use it.
EOS
when 'gmock', 'googlemock', 'google-mock' then <<-EOS.undent
Installing gmock system-wide is not recommended; it should be vendored
in your projects that use it.
EOS
when 'sshpass' then <<-EOS.undent
We won't add sshpass because it makes it too easy for novice SSH users to
ruin SSH's security.
EOS
when 'gsutil' then <<-EOS.undent
Install gsutil with `pip install gsutil`
EOS
when 'clojure' then <<-EOS.undent
Clojure isn't really a program but a library managed as part of a
project and Leiningen is the user interface to that library.
To install Clojure you should install Leiningen:
brew install leiningen
and then follow the tutorial:
https://github.com/technomancy/leiningen/blob/stable/doc/TUTORIAL.md
EOS
when 'rubinius' then <<-EOS.undent
Rubinius requires an existing Ruby 2.0 to bootstrap.
Doing this through Homebrew is error-prone. Instead, consider using
`ruby-build` to build and install specific versions of Ruby:
brew install ruby-build
EOS
when 'osmium' then <<-EOS.undent
The creator of Osmium requests that it not be packaged and that people
use the GitHub master branch instead.
EOS
when 'gfortran' then <<-EOS.undent
GNU Fortran is now provided as part of GCC, and can be installed with:
brew install gcc
EOS
when 'play' then <<-EOS.undent
Play 2.3 replaces the play command with activator:
brew install typesafe-activator
You can read more about this change at:
http://www.playframework.com/documentation/2.3.x/Migration23
http://www.playframework.com/documentation/2.3.x/Highlights23
EOS
when 'haskell-platform' then <<-EOS.undent
We no longer package haskell-platform. Consider installing ghc
and cabal-install instead:
brew install ghc cabal-install
A binary installer is available:
https://www.haskell.org/platform/mac.html
EOS
end
end

View File

@ -0,0 +1,38 @@
class BottleVersion < Version
def self._parse spec
spec = Pathname.new(spec) unless spec.is_a? Pathname
stem = spec.stem
# e.g. perforce-2013.1.610569-x86_64.mountain_lion.bottle.tar.gz
m = /-([\d\.]+-x86(_64)?)/.match(stem)
return m.captures.first unless m.nil?
# e.g. x264-r2197.4.mavericks.bottle.tar.gz
# e.g. lz4-r114.mavericks.bottle.tar.gz
m = /-(r\d+\.?\d*)/.match(stem)
return m.captures.first unless m.nil?
# e.g. 00-5.0.5 from zpython-00-5.0.5.mavericks.bottle.tar.gz
m = /(00-\d+\.\d+(\.\d+)+)/.match(stem)
return m.captures.first unless m.nil?
# e.g. 1.6.39 from pazpar2-1.6.39.mavericks.bottle.tar.gz
m = /-(\d+\.\d+(\.\d+)+)/.match(stem)
return m.captures.first unless m.nil?
# e.g. ssh-copy-id-6.2p2.mountain_lion.bottle.tar.gz
# e.g. icu4c-52.1.mountain_lion.bottle.tar.gz
m = /-(\d+\.(\d)+(p(\d)+)?)/.match(stem)
return m.captures.first unless m.nil?
# e.g. 0_5_0 from disco-0_5_0.mavericks.bottle.tar.gz
m = /-(\d+_\d+(_\d+)+)/.match(stem)
return m.captures.first unless m.nil?
# e.g. 20120731 from fontforge-20120731.mavericks.bottle.tar.gz
m = /-(\d{8})/.match(stem)
return m.captures.first unless m.nil?
super
end
end

View File

@ -0,0 +1,113 @@
require 'tab'
require 'os/mac'
require 'extend/ARGV'
require 'bottle_version'
def built_as_bottle? f
return false unless f.installed?
tab = Tab.for_keg(f.installed_prefix)
tab.built_as_bottle
end
def bottle_file_outdated? f, file
filename = file.basename.to_s
return unless f.bottle && filename.match(Pathname::BOTTLE_EXTNAME_RX)
bottle_ext = filename[bottle_native_regex, 1]
bottle_url_ext = f.bottle.url[bottle_native_regex, 1]
bottle_ext && bottle_url_ext && bottle_ext != bottle_url_ext
end
def bottle_native_regex
/(\.#{bottle_tag}\.bottle\.(\d+\.)?tar\.gz)$/o
end
def bottle_tag
if MacOS.version >= :lion
MacOS.cat
elsif MacOS.version == :snow_leopard
Hardware::CPU.is_64_bit? ? :snow_leopard : :snow_leopard_32
else
# Return, e.g., :tiger_g3, :leopard_g5_64, :leopard_64 (which is Intel)
if Hardware::CPU.type == :ppc
tag = "#{MacOS.cat}_#{Hardware::CPU.family}".to_sym
else
tag = MacOS.cat
end
MacOS.prefer_64_bit? ? "#{tag}_64".to_sym : tag
end
end
def bottle_filename_formula_name filename
path = Pathname.new filename
version = BottleVersion.parse(path).to_s
basename = path.basename.to_s
basename.rpartition("-#{version}").first
end
class BottleCollector
def initialize
@checksums = {}
end
def fetch_checksum_for(tag)
tag = find_matching_tag(tag)
return self[tag], tag if tag
end
def keys
@checksums.keys
end
def [](key)
@checksums[key]
end
def []=(key, value)
@checksums[key] = value
end
def key?(key)
@checksums.key?(key)
end
private
def find_matching_tag(tag)
if key?(tag)
tag
else
find_altivec_tag(tag) || find_or_later_tag(tag)
end
end
# This allows generic Altivec PPC bottles to be supported in some
# formulae, while also allowing specific bottles in others; e.g.,
# sometimes a formula has just :tiger_altivec, other times it has
# :tiger_g4, :tiger_g5, etc.
def find_altivec_tag(tag)
if tag.to_s =~ /(\w+)_(g4|g4e|g5)$/
altivec_tag = "#{$1}_altivec".to_sym
altivec_tag if key?(altivec_tag)
end
end
# Allows a bottle tag to specify a specific OS or later,
# so the same bottle can target multiple OSs.
# Not used in core, used in taps.
def find_or_later_tag(tag)
begin
tag_version = MacOS::Version.from_symbol(tag)
rescue ArgumentError
return
end
keys.find do |key|
if key.to_s.end_with?("_or_later")
later_tag = key.to_s[/(\w+)_or_later$/, 1].to_sym
MacOS::Version.from_symbol(later_tag) <= tag_version
end
end
end
end

View File

@ -0,0 +1,191 @@
# This script is loaded by formula_installer as a separate instance.
# Thrown exceptions are propogated back to the parent process over a pipe
old_trap = trap("INT") { exit! 130 }
require "global"
require "build_options"
require "cxxstdlib"
require "keg"
require "extend/ENV"
require "debrew"
require "fcntl"
class Build
attr_reader :formula, :deps, :reqs
def initialize(formula, options)
@formula = formula
@formula.build = BuildOptions.new(options, formula.options)
if ARGV.ignore_deps?
@deps = []
@reqs = []
else
@deps = expand_deps
@reqs = expand_reqs
end
end
def post_superenv_hacks
# Only allow Homebrew-approved directories into the PATH, unless
# a formula opts-in to allowing the user's path.
if formula.env.userpaths? || reqs.any? { |rq| rq.env.userpaths? }
ENV.userpaths!
end
end
def pre_superenv_hacks
# Allow a formula to opt-in to the std environment.
if (formula.env.std? || deps.any? { |d| d.name == "scons" }) && ARGV.env != "super"
ARGV.unshift "--env=std"
end
end
def effective_build_options_for(dependent)
args = dependent.build.used_options
args |= Tab.for_formula(dependent).used_options
BuildOptions.new(args, dependent.options)
end
def expand_reqs
formula.recursive_requirements do |dependent, req|
build = effective_build_options_for(dependent)
if (req.optional? || req.recommended?) && build.without?(req)
Requirement.prune
elsif req.build? && dependent != formula
Requirement.prune
elsif req.satisfied? && req.default_formula? && (dep = req.to_dependency).installed?
deps << dep
Requirement.prune
end
end
end
def expand_deps
formula.recursive_dependencies do |dependent, dep|
build = effective_build_options_for(dependent)
if (dep.optional? || dep.recommended?) && build.without?(dep)
Dependency.prune
elsif dep.build? && dependent != formula
Dependency.prune
elsif dep.build?
Dependency.keep_but_prune_recursive_deps
end
end
end
def install
keg_only_deps = deps.map(&:to_formula).select(&:keg_only?)
deps.map(&:to_formula).each do |dep|
fixopt(dep) unless dep.opt_prefix.directory?
end
pre_superenv_hacks
ENV.activate_extensions!
if superenv?
ENV.keg_only_deps = keg_only_deps.map(&:name)
ENV.deps = deps.map { |d| d.to_formula.name }
ENV.x11 = reqs.any? { |rq| rq.kind_of?(X11Dependency) }
ENV.setup_build_environment(formula)
post_superenv_hacks
reqs.each(&:modify_build_environment)
deps.each(&:modify_build_environment)
else
ENV.setup_build_environment(formula)
reqs.each(&:modify_build_environment)
deps.each(&:modify_build_environment)
keg_only_deps.each do |dep|
ENV.prepend_path "PATH", dep.opt_bin.to_s
ENV.prepend_path "PKG_CONFIG_PATH", "#{dep.opt_lib}/pkgconfig"
ENV.prepend_path "PKG_CONFIG_PATH", "#{dep.opt_share}/pkgconfig"
ENV.prepend_path "ACLOCAL_PATH", "#{dep.opt_share}/aclocal"
ENV.prepend_path "CMAKE_PREFIX_PATH", dep.opt_prefix.to_s
ENV.prepend "LDFLAGS", "-L#{dep.opt_lib}" if dep.opt_lib.directory?
ENV.prepend "CPPFLAGS", "-I#{dep.opt_include}" if dep.opt_include.directory?
end
end
if ARGV.debug?
formula.extend(Debrew::Formula)
formula.resources.each { |r| r.extend(Debrew::Resource) }
end
formula.brew do
if ARGV.flag? '--git'
system "git", "init"
system "git", "add", "-A"
end
if ARGV.interactive?
ohai "Entering interactive mode"
puts "Type `exit' to return and finalize the installation"
puts "Install to this prefix: #{formula.prefix}"
if ARGV.flag? '--git'
puts "This directory is now a git repo. Make your changes and then use:"
puts " git diff | pbcopy"
puts "to copy the diff to the clipboard."
end
interactive_shell(formula)
else
formula.prefix.mkpath
formula.install
stdlibs = detect_stdlibs
Tab.create(formula, ENV.compiler, stdlibs.first, formula.build).write
# Find and link metafiles
formula.prefix.install_metafiles Pathname.pwd
end
end
end
def detect_stdlibs
keg = Keg.new(formula.prefix)
CxxStdlib.check_compatibility(formula, deps, keg, ENV.compiler)
# The stdlib recorded in the install receipt is used during dependency
# compatibility checks, so we only care about the stdlib that libraries
# link against.
keg.detect_cxx_stdlibs(:skip_executables => true)
end
def fixopt f
path = if f.linked_keg.directory? and f.linked_keg.symlink?
f.linked_keg.resolved_path
elsif f.prefix.directory?
f.prefix
elsif (kids = f.rack.children).size == 1 and kids.first.directory?
kids.first
else
raise
end
Keg.new(path).optlink
rescue StandardError
raise "#{f.opt_prefix} not present or broken\nPlease reinstall #{f.name}. Sorry :("
end
end
begin
error_pipe = IO.new(ENV["HOMEBREW_ERROR_PIPE"].to_i, "w")
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
# Invalidate the current sudo timestamp in case a build script calls sudo
system "/usr/bin/sudo", "-k"
trap("INT", old_trap)
formula = ARGV.formulae.first
options = Options.create(ARGV.flags_only)
build = Build.new(formula, options)
build.install
rescue Exception => e
Marshal.dump(e, error_pipe)
error_pipe.close
exit! 1
end

View File

@ -0,0 +1,32 @@
require 'set'
class BuildEnvironment
def initialize(*settings)
@settings = Set.new(*settings)
end
def merge(*args)
@settings.merge(*args)
self
end
def <<(o)
@settings << o
self
end
def std?
@settings.include? :std
end
def userpaths?
@settings.include? :userpaths
end
end
module BuildEnvironmentDSL
def env(*settings)
@env ||= BuildEnvironment.new
@env.merge(settings)
end
end

View File

@ -0,0 +1,77 @@
class BuildOptions
def initialize(args, options)
@args = args
@options = options
end
def include? name
@args.include?("--#{name}")
end
def with? val
if val.respond_to?(:option_name)
name = val.option_name
else
name = val
end
if option_defined? "with-#{name}"
include? "with-#{name}"
elsif option_defined? "without-#{name}"
not include? "without-#{name}"
else
false
end
end
def without? name
not with? name
end
def bottle?
include? "build-bottle"
end
def head?
include? "HEAD"
end
def devel?
include? "devel"
end
def stable?
not (head? or devel?)
end
# True if the user requested a universal build.
def universal?
include?("universal") && option_defined?("universal")
end
# True if the user requested to enable C++11 mode.
def cxx11?
include?("c++11") && option_defined?("c++11")
end
# Request a 32-bit only build.
# This is needed for some use-cases though we prefer to build Universal
# when a 32-bit version is needed.
def build_32_bit?
include?("32-bit") && option_defined?("32-bit")
end
def used_options
@options & @args
end
def unused_options
@options - @args
end
private
def option_defined? name
@options.include? name
end
end

View File

@ -0,0 +1,132 @@
class Caveats
attr_reader :f
def initialize(f)
@f = f
end
def caveats
caveats = []
caveats << f.caveats if f.caveats.to_s.length > 0
caveats << f.keg_only_text if f.keg_only? && f.respond_to?(:keg_only_text)
caveats << bash_completion_caveats
caveats << zsh_completion_caveats
caveats << plist_caveats
caveats << python_caveats
caveats << app_caveats
caveats.compact.join("\n")
end
def empty?
caveats.empty?
end
private
def keg
@keg ||= [f.prefix, f.opt_prefix, f.linked_keg].map do |d|
Keg.new(d.resolved_path) rescue nil
end.compact.first
end
def bash_completion_caveats
if keg and keg.completion_installed? :bash then <<-EOS.undent
Bash completion has been installed to:
#{HOMEBREW_PREFIX}/etc/bash_completion.d
EOS
end
end
def zsh_completion_caveats
if keg and keg.completion_installed? :zsh then <<-EOS.undent
zsh completion has been installed to:
#{HOMEBREW_PREFIX}/share/zsh/site-functions
EOS
end
end
def python_caveats
return unless keg
return unless keg.python_site_packages_installed?
return if Formula["python"].installed?
site_packages = if f.keg_only?
"#{f.opt_prefix}/lib/python2.7/site-packages"
else
"#{HOMEBREW_PREFIX}/lib/python2.7/site-packages"
end
dir = "~/Library/Python/2.7/lib/python/site-packages"
dir_path = Pathname.new(dir).expand_path
file = "#{dir}/homebrew.pth"
file_path = Pathname.new(file).expand_path
if !file_path.readable? || !file_path.read.include?(site_packages)
s = "If you need Python to find the installed site-packages:\n"
s += " mkdir -p #{dir}\n" unless dir_path.exist?
s += " echo '#{site_packages}' > #{file}"
end
end
def app_caveats
if keg and keg.app_installed?
<<-EOS.undent
.app bundles were installed.
Run `brew linkapps` to symlink these to /Applications.
EOS
end
end
def plist_caveats
s = []
if f.plist or (keg and keg.plist_installed?)
destination = f.plist_startup ? '/Library/LaunchDaemons' \
: '~/Library/LaunchAgents'
plist_filename = f.plist_path.basename
plist_link = "#{destination}/#{plist_filename}"
plist_domain = f.plist_path.basename('.plist')
destination_path = Pathname.new File.expand_path destination
plist_path = destination_path/plist_filename
# we readlink because this path probably doesn't exist since caveats
# occurs before the link step of installation
if !plist_path.file? || !plist_path.symlink?
if f.plist_startup
s << "To have launchd start #{f.name} at startup:"
s << " sudo mkdir -p #{destination}" unless destination_path.directory?
s << " sudo cp -fv #{f.opt_prefix}/*.plist #{destination}"
else
s << "To have launchd start #{f.name} at login:"
s << " mkdir -p #{destination}" unless destination_path.directory?
s << " ln -sfv #{f.opt_prefix}/*.plist #{destination}"
end
s << "Then to load #{f.name} now:"
if f.plist_startup
s << " sudo launchctl load #{plist_link}"
else
s << " launchctl load #{plist_link}"
end
# For startup plists, we cannot tell whether it's running on launchd,
# as it requires for `sudo launchctl list` to get real result.
elsif f.plist_startup
s << "To reload #{f.name} after an upgrade:"
s << " sudo launchctl unload #{plist_link}"
s << " sudo cp -fv #{f.opt_prefix}/*.plist #{destination}"
s << " sudo launchctl load #{plist_link}"
elsif Kernel.system "/bin/launchctl list #{plist_domain} &>/dev/null"
s << "To reload #{f.name} after an upgrade:"
s << " launchctl unload #{plist_link}"
s << " launchctl load #{plist_link}"
else
s << "To load #{f.name}:"
s << " launchctl load #{plist_link}"
end
if f.plist_manual
s << "Or, if you don't want/need launchctl, you can just run:"
s << " #{f.plist_manual}"
end
s << "" << "WARNING: launchctl will fail when run under tmux." if ENV['TMUX']
end
s.join("\n") unless s.empty?
end
end

View File

@ -0,0 +1,19 @@
class Checksum
attr_reader :hash_type, :hexdigest
alias_method :to_s, :hexdigest
TYPES = [:sha1, :sha256]
def initialize(hash_type, hexdigest)
@hash_type = hash_type
@hexdigest = hexdigest
end
def empty?
hexdigest.empty?
end
def ==(other)
hash_type == other.hash_type && hexdigest == other.hexdigest
end
end

View File

@ -0,0 +1,107 @@
# Cleans a newly installed keg.
# By default:
# * removes .la files
# * removes empty directories
# * sets permissions on executables
# * removes unresolved symlinks
class Cleaner
# Create a cleaner for the given formula
def initialize f
@f = f
end
# Clean the keg of formula @f
def clean
ObserverPathnameExtension.reset_counts!
# Many formulae include 'lib/charset.alias', but it is not strictly needed
# and will conflict if more than one formula provides it
observe_file_removal @f.lib/'charset.alias'
[@f.bin, @f.sbin, @f.lib].select{ |d| d.exist? }.each{ |d| clean_dir d }
# Get rid of any info 'dir' files, so they don't conflict at the link stage
info_dir_file = @f.info + 'dir'
if info_dir_file.file? and not @f.skip_clean? info_dir_file
observe_file_removal info_dir_file
end
prune
end
private
def observe_file_removal path
path.extend(ObserverPathnameExtension).unlink if path.exist?
end
# Removes any empty directories in the formula's prefix subtree
# Keeps any empty directions projected by skip_clean
# Removes any unresolved symlinks
def prune
dirs = []
symlinks = []
@f.prefix.find do |path|
if path == @f.libexec or @f.skip_clean?(path)
Find.prune
elsif path.symlink?
symlinks << path
elsif path.directory?
dirs << path
end
end
# Remove directories opposite from traversal, so that a subtree with no
# actual files gets removed correctly.
dirs.reverse_each do |d|
if d.children.empty?
puts "rmdir: #{d} (empty)" if ARGV.verbose?
d.rmdir
end
end
# Remove unresolved symlinks
symlinks.reverse_each do |s|
s.unlink unless s.resolved_path_exists?
end
end
# Clean a top-level (bin, sbin, lib) directory, recursively, by fixing file
# permissions and removing .la files, unless the files (or parent
# directories) are protected by skip_clean.
#
# bin and sbin should not have any subdirectories; if either do that is
# caught as an audit warning
#
# lib may have a large directory tree (see Erlang for instance), and
# clean_dir applies cleaning rules to the entire tree
def clean_dir d
d.find do |path|
path.extend(ObserverPathnameExtension)
Find.prune if @f.skip_clean? path
if path.symlink? or path.directory?
next
elsif path.extname == '.la'
path.unlink
else
# Set permissions for executables and non-executables
perms = if path.mach_o_executable? || path.text_executable?
0555
else
0444
end
if ARGV.debug?
old_perms = path.stat.mode & 0777
if perms != old_perms
puts "Fixing #{path} permissions from #{old_perms.to_s(8)} to #{perms.to_s(8)}"
end
end
path.chmod perms
end
end
end
end

View File

@ -0,0 +1,145 @@
require "metafiles"
module Homebrew
def list
# Use of exec means we don't explicitly exit
list_unbrewed if ARGV.flag? '--unbrewed'
# Unbrewed uses the PREFIX, which will exist
# Things below use the CELLAR, which doesn't until the first formula is installed.
return unless HOMEBREW_CELLAR.exist?
if ARGV.include? '--pinned' or ARGV.include? '--versions'
filtered_list
elsif ARGV.named.empty?
ENV['CLICOLOR'] = nil
exec 'ls', *ARGV.options_only << HOMEBREW_CELLAR
elsif ARGV.verbose? or not $stdout.tty?
exec "find", *ARGV.kegs.map(&:to_s) + %w[-not -type d -print]
else
ARGV.kegs.each{ |keg| PrettyListing.new keg }
end
end
private
UNBREWED_EXCLUDE_FILES = %w[.DS_Store]
UNBREWED_EXCLUDE_PATHS = %w[
bin/brew
lib/gdk-pixbuf-2.0/*
lib/gio/*
lib/node_modules/*
lib/python[23].[0-9]/*
share/info/dir
share/man/man1/brew.1
share/man/whatis
]
def list_unbrewed
dirs = HOMEBREW_PREFIX.subdirs.map { |dir| dir.basename.to_s }
dirs -= %w[Library Cellar .git]
# Exclude the repository and cache, if they are located under the prefix
dirs.delete HOMEBREW_CACHE.relative_path_from(HOMEBREW_PREFIX).to_s
dirs.delete HOMEBREW_REPOSITORY.relative_path_from(HOMEBREW_PREFIX).to_s
dirs.delete 'etc'
dirs.delete 'var'
args = dirs + %w[-type f (]
args.concat UNBREWED_EXCLUDE_FILES.map { |f| %W[! -name #{f}] }.flatten
args.concat UNBREWED_EXCLUDE_PATHS.map { |d| %W[! -path #{d}] }.flatten
args.concat %w[)]
cd HOMEBREW_PREFIX
exec 'find', *args
end
def filtered_list
names = if ARGV.named.empty?
HOMEBREW_CELLAR.subdirs
else
ARGV.named.map{ |n| HOMEBREW_CELLAR+n }.select{ |pn| pn.exist? }
end
if ARGV.include? '--pinned'
pinned_versions = {}
names.each do |d|
keg_pin = (HOMEBREW_LIBRARY/"PinnedKegs"/d.basename.to_s)
if keg_pin.exist? or keg_pin.symlink?
pinned_versions[d] = keg_pin.readlink.basename.to_s
end
end
pinned_versions.each do |d, version|
puts "#{d.basename}".concat(ARGV.include?('--versions') ? " #{version}" : '')
end
else # --versions without --pinned
names.each do |d|
versions = d.subdirs.map { |pn| pn.basename.to_s }
next if ARGV.include?('--multiple') && versions.count < 2
puts "#{d.basename} #{versions*' '}"
end
end
end
end
class PrettyListing
def initialize path
Pathname.new(path).children.sort_by { |p| p.to_s.downcase }.each do |pn|
case pn.basename.to_s
when 'bin', 'sbin'
pn.find { |pnn| puts pnn unless pnn.directory? }
when 'lib'
print_dir pn do |pnn|
# dylibs have multiple symlinks and we don't care about them
(pnn.extname == '.dylib' or pnn.extname == '.pc') and not pnn.symlink?
end
else
if pn.directory?
if pn.symlink?
puts "#{pn} -> #{pn.readlink}"
else
print_dir pn
end
elsif Metafiles.list?(pn.basename.to_s)
puts pn
end
end
end
end
def print_dir root
dirs = []
remaining_root_files = []
other = ''
root.children.sort.each do |pn|
if pn.directory?
dirs << pn
elsif block_given? and yield pn
puts pn
other = 'other '
else
remaining_root_files << pn unless pn.basename.to_s == '.DS_Store'
end
end
dirs.each do |d|
files = []
d.find { |pn| files << pn unless pn.directory? }
print_remaining_files files, d
end
print_remaining_files remaining_root_files, root, other
end
def print_remaining_files files, root, other = ''
case files.length
when 0
# noop
when 1
puts files
else
puts "#{root}/ (#{files.length} #{other}files)"
end
end
end

View File

@ -0,0 +1,133 @@
module Homebrew
def tap
if ARGV.empty?
each_tap do |user, repo|
puts "#{user.basename}/#{repo.basename.sub("homebrew-", "")}" if (repo/".git").directory?
end
elsif ARGV.first == "--repair"
repair_taps
else
opoo "Already tapped!" unless install_tap(*tap_args)
end
end
def install_tap user, repo
# we special case homebrew so users don't have to shift in a terminal
repouser = if user == "homebrew" then "Homebrew" else user end
user = "homebrew" if user == "Homebrew"
# we downcase to avoid case-insensitive filesystem issues
tapd = HOMEBREW_LIBRARY/"Taps/#{user.downcase}/homebrew-#{repo.downcase}"
return false if tapd.directory?
abort unless system "git", "clone", "https://github.com/#{repouser}/homebrew-#{repo}", tapd.to_s
files = []
tapd.find_formula { |file| files << file }
link_tap_formula(files)
puts "Tapped #{files.length} formula#{plural(files.length, 'e')}"
if private_tap?(repouser, repo) then puts <<-EOS.undent
It looks like you tapped a private repository. To avoid entering your
credentials each time you update, you can use git HTTP credential caching
or issue the following command:
cd #{tapd}
git remote set-url origin git@github.com:#{repouser}/homebrew-#{repo}.git
EOS
end
true
end
def link_tap_formula(paths, warn_about_conflicts=true)
ignores = (HOMEBREW_LIBRARY/"Formula/.gitignore").read.split rescue []
tapped = 0
paths.each do |path|
to = HOMEBREW_LIBRARY.join("Formula", path.basename)
# Unexpected, but possible, lets proceed as if nothing happened
to.delete if to.symlink? && to.resolved_path == path
begin
to.make_relative_symlink(path)
rescue SystemCallError
to = to.resolved_path if to.symlink?
opoo <<-EOS.undent if warn_about_conflicts
Could not create link for #{Tty.white}#{tap_ref(path)}#{Tty.reset}, as it
conflicts with #{Tty.white}#{tap_ref(to)}#{Tty.reset}. You will need to use the
fully-qualified name when referring this formula, e.g.
brew install #{tap_ref(path)}
EOS
else
ignores << path.basename.to_s
tapped += 1
end
end
HOMEBREW_LIBRARY.join("Formula/.gitignore").atomic_write(ignores.uniq.join("\n"))
tapped
end
def repair_taps(warn_about_conflicts=true)
count = 0
# prune dead symlinks in Formula
Dir.glob("#{HOMEBREW_LIBRARY}/Formula/*.rb") do |fn|
if not File.exist? fn
File.delete fn
count += 1
end
end
puts "Pruned #{count} dead formula#{plural(count, 'e')}"
return unless HOMEBREW_REPOSITORY.join("Library/Taps").exist?
count = 0
# check symlinks are all set in each tap
each_tap do |user, repo|
files = []
repo.find_formula { |file| files << file }
count += link_tap_formula(files, warn_about_conflicts)
end
puts "Tapped #{count} formula#{plural(count, 'e')}"
end
private
def each_tap
taps = HOMEBREW_LIBRARY.join("Taps")
if taps.directory?
taps.subdirs.each do |user|
user.subdirs.each do |repo|
yield user, repo
end
end
end
end
def tap_args
ARGV.first =~ HOMEBREW_TAP_ARGS_REGEX
raise "Invalid tap name" unless $1 && $3
[$1, $3]
end
def private_tap?(user, repo)
GitHub.private_repo?(user, "homebrew-#{repo}")
rescue GitHub::HTTPNotFoundError
true
rescue GitHub::Error
false
end
def tap_ref(path)
case path.to_s
when %r{^#{Regexp.escape(HOMEBREW_LIBRARY.to_s)}/Formula}o
"Homebrew/homebrew/#{path.basename(".rb")}"
when HOMEBREW_TAP_PATH_REGEX
"#{$1}/#{$2.sub("homebrew-", "")}/#{path.basename(".rb")}"
end
end
end

View File

@ -0,0 +1,6 @@
require 'compat/fails_with_llvm'
require 'compat/formula'
require 'compat/hardware'
require 'compat/macos'
require 'compat/md5'
require 'compat/version'

View File

@ -0,0 +1,11 @@
class Formula
def fails_with_llvm msg=nil, data=nil
opoo "Calling fails_with_llvm in the install method is deprecated"
puts "Use the fails_with DSL instead"
end
def self.fails_with_llvm msg=nil, data={}
data = msg if Hash === msg
fails_with(:llvm) { build(data.delete(:build).to_i) }
end
end

View File

@ -0,0 +1,46 @@
module FormulaCompat
def x11_installed?
MacOS::X11.installed?
end
def snow_leopard_64?
MacOS.prefer_64_bit?
end
end
class Formula
include FormulaCompat
extend FormulaCompat
def std_cmake_parameters
"-DCMAKE_INSTALL_PREFIX='#{prefix}' -DCMAKE_BUILD_TYPE=None -DCMAKE_FIND_FRAMEWORK=LAST -Wno-dev"
end
def cxxstdlib_check check_type
self.class.cxxstdlib_check check_type
end
def self.bottle_sha1(*)
end
def self.all
opoo "Formula.all is deprecated, use Formula.map instead"
map
end
def self.canonical_name(name)
Formulary.canonical_name(name)
end
def self.class_s(name)
Formulary.class_s(name)
end
def self.factory(name)
Formulary.factory(name)
end
def self.require_universal_deps
define_method(:require_universal_deps?) { true }
end
end

View File

@ -0,0 +1,29 @@
class Hardware
class << self
def is_32_bit?
not CPU.is_64_bit?
end
def is_64_bit?
CPU.is_64_bit?
end
def bits
Hardware::CPU.bits
end
def cpu_type
Hardware::CPU.type
end
def cpu_family
Hardware::CPU.family
end
alias_method :intel_family, :cpu_family
alias_method :ppc_family, :cpu_family
def processor_count
Hardware::CPU.cores
end
end
end

View File

@ -0,0 +1,58 @@
module OS
module Mac
def xcode_folder
Xcode.folder
end
def xcode_prefix
Xcode.prefix
end
def xcode_installed?
Xcode.installed?
end
def xcode_version
Xcode.version
end
def clt_installed?
CLT.installed?
end
def clt_version?
CLT.version
end
def x11_installed?
X11.installed?
end
def x11_prefix
X11.prefix
end
def leopard?
version == "10.5"
end
def snow_leopard?
version >= "10.6"
end
alias_method :snow_leopard_or_newer?, :snow_leopard?
def lion?
version >= "10.7"
end
alias_method :lion_or_newer?, :lion?
def mountain_lion?
version >= "10.8"
end
alias_method :mountain_lion_or_newer?, :mountain_lion?
def macports_or_fink_installed?
!macports_or_fink.empty?
end
end
end

View File

@ -0,0 +1,28 @@
class Formula
def self.md5(val)
stable.md5(val)
end
end
class SoftwareSpec
def md5(val)
@resource.md5(val)
end
end
class Resource
def md5(val)
@checksum = Checksum.new(:md5, val)
end
end
class Pathname
def md5
require 'digest/md5'
opoo <<-EOS.undent
MD5 support is deprecated and will be removed in a future version.
Please switch this formula to #{Checksum::TYPES.map { |t| t.to_s.upcase } * ' or '}.
EOS
incremental_hash(Digest::MD5)
end
end

View File

@ -0,0 +1,6 @@
class Version
def slice *args
opoo "Calling slice on versions is deprecated, use: to_s.slice"
to_s.slice(*args)
end
end

View File

@ -0,0 +1,130 @@
module CompilerConstants
GNU_GCC_VERSIONS = 3..9
GNU_GCC_REGEXP = /^gcc-(4\.[3-9])$/
end
class CompilerFailure
attr_reader :name
attr_rw :version
# Allows Apple compiler `fails_with` statements to keep using `build`
# even though `build` and `version` are the same internally
alias_method :build, :version
# The cause is no longer used so we need not hold a reference to the string
def cause(_); end
def self.for_standard standard
COLLECTIONS.fetch(standard) do
raise ArgumentError, "\"#{standard}\" is not a recognized standard"
end
end
def self.create(spec, &block)
# Non-Apple compilers are in the format fails_with compiler => version
if spec.is_a?(Hash)
_, major_version = spec.each { |e| break e }
name = "gcc-#{major_version}"
# so fails_with :gcc => '4.8' simply marks all 4.8 releases incompatible
version = "#{major_version}.999"
else
name = spec
version = 9999
end
new(name, version, &block)
end
def initialize(name, version, &block)
@name = name
@version = version
instance_eval(&block) if block_given?
end
def ===(compiler)
name == compiler.name && version >= compiler.version
end
def inspect
"#<#{self.class.name}: #{name} #{version}>"
end
COLLECTIONS = {
:cxx11 => [
create(:gcc_4_0),
create(:gcc),
create(:llvm),
create(:clang) { build 425 },
create(:gcc => "4.3"),
create(:gcc => "4.4"),
create(:gcc => "4.5"),
create(:gcc => "4.6"),
],
:openmp => [create(:clang)],
}
end
class CompilerSelector
include CompilerConstants
Compiler = Struct.new(:name, :version)
COMPILER_PRIORITY = {
:clang => [:clang, :gcc, :llvm, :gnu, :gcc_4_0],
:gcc => [:gcc, :llvm, :gnu, :clang, :gcc_4_0],
:llvm => [:llvm, :gcc, :gnu, :clang, :gcc_4_0],
:gcc_4_0 => [:gcc_4_0, :gcc, :llvm, :gnu, :clang],
}
def self.select_for(formula, compilers=self.compilers)
new(formula, MacOS, compilers).compiler
end
def self.compilers
COMPILER_PRIORITY.fetch(MacOS.default_compiler)
end
attr_reader :formula, :failures, :versions, :compilers
def initialize(formula, versions, compilers)
@formula = formula
@failures = formula.compiler_failures
@versions = versions
@compilers = compilers
end
def compiler
find_compiler { |c| return c.name unless fails_with?(c) }
raise CompilerSelectionError.new(formula)
end
private
def find_compiler
compilers.each do |compiler|
case compiler
when :gnu
GNU_GCC_VERSIONS.reverse_each do |v|
name = "gcc-4.#{v}"
version = compiler_version(name)
yield Compiler.new(name, version) if version
end
else
version = compiler_version(compiler)
yield Compiler.new(compiler, version) if version
end
end
end
def fails_with?(compiler)
failures.any? { |failure| failure === compiler }
end
def compiler_version(name)
case name
when GNU_GCC_REGEXP
versions.non_apple_gcc_version(name)
else
versions.send("#{name}_build_version")
end
end
end

View File

@ -0,0 +1,87 @@
require "compilers"
class CxxStdlib
include CompilerConstants
class CompatibilityError < StandardError
def initialize(formula, dep, stdlib)
super <<-EOS.undent
#{formula.name} dependency #{dep.name} was built with a different C++ standard
library (#{stdlib.type_string} from #{stdlib.compiler}). This may cause problems at runtime.
EOS
end
end
def self.create(type, compiler)
if type && ![:libstdcxx, :libcxx].include?(type)
raise ArgumentError, "Invalid C++ stdlib type: #{type}"
end
klass = GNU_GCC_REGEXP === compiler.to_s ? GnuStdlib : AppleStdlib
klass.new(type, compiler)
end
def self.check_compatibility(formula, deps, keg, compiler)
return if formula.skip_cxxstdlib_check?
stdlib = create(keg.detect_cxx_stdlibs.first, compiler)
begin
stdlib.check_dependencies(formula, deps)
rescue CompatibilityError => e
opoo e.message
end
end
attr_reader :type, :compiler
def initialize(type, compiler)
@type = type
@compiler = compiler.to_sym
end
# If either package doesn't use C++, all is well
# libstdc++ and libc++ aren't ever intercompatible
# libstdc++ is compatible across Apple compilers, but
# not between Apple and GNU compilers, or between GNU compiler versions
def compatible_with?(other)
return true if type.nil? || other.type.nil?
return false unless type == other.type
apple_compiler? && other.apple_compiler? ||
!other.apple_compiler? && compiler.to_s[4..6] == other.compiler.to_s[4..6]
end
def check_dependencies(formula, deps)
deps.each do |dep|
# Software is unlikely to link against libraries from build-time deps, so
# it doesn't matter if they link against different C++ stdlibs.
next if dep.build?
dep_stdlib = Tab.for_formula(dep.to_formula).cxxstdlib
if !compatible_with? dep_stdlib
raise CompatibilityError.new(formula, dep, dep_stdlib)
end
end
end
def type_string
type.to_s.gsub(/cxx$/, 'c++')
end
def inspect
"#<#{self.class.name}: #{compiler} #{type}>"
end
class AppleStdlib < CxxStdlib
def apple_compiler?
true
end
end
class GnuStdlib < CxxStdlib
def apple_compiler?
false
end
end
end

View File

@ -0,0 +1,158 @@
require "mutex_m"
module Debrew
extend Mutex_m
Ignorable = Module.new
module Raise
def raise(*)
super
rescue Exception => e
e.extend(Ignorable)
super(e) unless Debrew.debug(e) == :ignore
end
alias_method :fail, :raise
end
module Formula
def install
Debrew.debrew { super }
end
def test
Debrew.debrew { super }
end
end
module Resource
def unpack(target=nil)
return super if target
super do
begin
yield self
rescue Exception => e
Debrew.debug(e)
end
end
end
end
class Menu
Entry = Struct.new(:name, :action)
attr_accessor :prompt, :entries
def initialize
@entries = []
end
def choice(name, &action)
entries << Entry.new(name.to_s, action)
end
def self.choose
menu = new
yield menu
choice = nil
while choice.nil?
menu.entries.each_with_index { |e, i| puts "#{i+1}. #{e.name}" }
print menu.prompt unless menu.prompt.nil?
input = $stdin.gets or exit
input.chomp!
i = input.to_i
if i > 0
choice = menu.entries[i-1]
else
possible = menu.entries.find_all { |e| e.name.start_with?(input) }
case possible.size
when 0 then puts "No such option"
when 1 then choice = possible.first
else puts "Multiple options match: #{possible.map(&:name).join(" ")}"
end
end
end
choice[:action].call
end
end
class << self
alias_method :original_raise, :raise
end
@active = false
@debugged_exceptions = Set.new
def self.active?
@active
end
def self.debugged_exceptions
@debugged_exceptions
end
def self.debrew
@active = true
Object.send(:include, Raise)
begin
yield
rescue SystemExit
original_raise
rescue Exception => e
debug(e)
ensure
@active = false
end
end
def self.debug(e)
original_raise(e) unless active? &&
debugged_exceptions.add?(e) &&
try_lock
begin
puts "#{e.backtrace.first}"
puts "#{Tty.red}#{e.class.name}#{Tty.reset}: #{e}"
loop do
Menu.choose do |menu|
menu.prompt = "Choose an action: "
menu.choice(:raise) { original_raise(e) }
menu.choice(:ignore) { return :ignore } if Ignorable === e
menu.choice(:backtrace) { puts e.backtrace }
unless ENV["HOMEBREW_NO_READLINE"]
require "debrew/irb"
menu.choice(:irb) do
puts "When you exit this IRB session, execution will continue."
set_trace_func proc { |event, _, _, id, binding, klass|
if klass == Raise && id == :raise && event == "return"
set_trace_func(nil)
synchronize { IRB.start_within(binding) }
end
}
return :ignore
end if Ignorable === e
end
menu.choice(:shell) do
puts "When you exit this shell, you will return to the menu."
interactive_shell
end
end
end
ensure
unlock
end
end
end

View File

@ -0,0 +1,29 @@
require 'options'
module Dependable
RESERVED_TAGS = [:build, :optional, :recommended, :run]
def build?
tags.include? :build
end
def optional?
tags.include? :optional
end
def recommended?
tags.include? :recommended
end
def run?
tags.include? :run
end
def required?
!build? && !optional? && !recommended?
end
def options
Options.create(tags - RESERVED_TAGS)
end
end

View File

@ -0,0 +1,79 @@
class Dependencies
include Enumerable
def initialize(*args)
@deps = Array.new(*args)
end
def each(*args, &block)
@deps.each(*args, &block)
end
def <<(o)
@deps << o unless include?(o)
self
end
def empty?
@deps.empty?
end
def *(arg)
@deps * arg
end
alias_method :to_ary, :to_a
def optional
select(&:optional?)
end
def recommended
select(&:recommended?)
end
def build
select(&:build?)
end
def required
select(&:required?)
end
def default
build + required + recommended
end
attr_reader :deps
protected :deps
def ==(other)
deps == other.deps
end
alias_method :eql?, :==
end
class Requirements
include Enumerable
def initialize(*args)
@reqs = Set.new(*args)
end
def each(*args, &block)
@reqs.each(*args, &block)
end
def <<(other)
if Comparable === other
@reqs.grep(other.class) do |req|
return self if req > other
@reqs.delete(req)
end
end
@reqs << other
self
end
alias_method :to_ary, :to_a
end

View File

@ -0,0 +1,142 @@
require 'dependable'
# A dependency on another Homebrew formula.
class Dependency
include Dependable
attr_reader :name, :tags, :env_proc, :option_name
DEFAULT_ENV_PROC = proc {}
def initialize(name, tags=[], env_proc=DEFAULT_ENV_PROC, option_name=name)
@name = name
@tags = tags
@env_proc = env_proc
@option_name = option_name
end
def to_s
name
end
def ==(other)
instance_of?(other.class) && name == other.name
end
alias_method :eql?, :==
def hash
name.hash
end
def to_formula
formula = Formulary.factory(name)
formula.build = BuildOptions.new(options, formula.options)
formula
end
def installed?
to_formula.installed?
end
def satisfied?(inherited_options)
installed? && missing_options(inherited_options).empty?
end
def missing_options(inherited_options)
required = options | inherited_options
required - Tab.for_formula(to_formula).used_options
end
def modify_build_environment
env_proc.call unless env_proc.nil?
end
def inspect
"#<#{self.class.name}: #{name.inspect} #{tags.inspect}>"
end
# Define marshaling semantics because we cannot serialize @env_proc
def _dump(*)
Marshal.dump([name, tags])
end
def self._load(marshaled)
new(*Marshal.load(marshaled))
end
class << self
# Expand the dependencies of dependent recursively, optionally yielding
# [dependent, dep] pairs to allow callers to apply arbitrary filters to
# the list.
# The default filter, which is applied when a block is not given, omits
# optionals and recommendeds based on what the dependent has asked for.
def expand(dependent, deps=dependent.deps, &block)
expanded_deps = []
deps.each do |dep|
# FIXME don't hide cyclic dependencies
next if dependent.name == dep.name
case action(dependent, dep, &block)
when :prune
next
when :skip
expanded_deps.concat(expand(dep.to_formula, &block))
when :keep_but_prune_recursive_deps
expanded_deps << dep
else
expanded_deps.concat(expand(dep.to_formula, &block))
expanded_deps << dep
end
end
merge_repeats(expanded_deps)
end
def action(dependent, dep, &block)
catch(:action) do
if block_given?
yield dependent, dep
elsif dep.optional? || dep.recommended?
prune unless dependent.build.with?(dep)
end
end
end
# Prune a dependency and its dependencies recursively
def prune
throw(:action, :prune)
end
# Prune a single dependency but do not prune its dependencies
def skip
throw(:action, :skip)
end
# Keep a dependency, but prune its dependencies
def keep_but_prune_recursive_deps
throw(:action, :keep_but_prune_recursive_deps)
end
def merge_repeats(deps)
grouped = deps.group_by(&:name)
deps.uniq.map do |dep|
tags = grouped.fetch(dep.name).map(&:tags).flatten.uniq
dep.class.new(dep.name, tags, dep.env_proc)
end
end
end
end
class TapDependency < Dependency
def initialize(name, tags=[], env_proc=DEFAULT_ENV_PROC, option_name=name)
super(name, tags, env_proc, name.split("/").last)
end
def installed?
super
rescue FormulaUnavailableError
false
end
end

View File

@ -0,0 +1,189 @@
require 'dependency'
require 'dependencies'
require 'requirement'
require 'requirements'
require 'requirements/ld64_dependency'
require 'set'
## A dependency is a formula that another formula needs to install.
## A requirement is something other than a formula that another formula
## needs to be present. This includes external language modules,
## command-line tools in the path, or any arbitrary predicate.
##
## The `depends_on` method in the formula DSL is used to declare
## dependencies and requirements.
# This class is used by `depends_on` in the formula DSL to turn dependency
# specifications into the proper kinds of dependencies and requirements.
class DependencyCollector
# Define the languages that we can handle as external dependencies.
LANGUAGE_MODULES = Set[
:chicken, :jruby, :lua, :node, :ocaml, :perl, :python, :rbx, :ruby
].freeze
CACHE = {}
def self.clear_cache
CACHE.clear
end
attr_reader :deps, :requirements
def initialize
@deps = Dependencies.new
@requirements = Requirements.new
end
def add(spec)
case dep = fetch(spec)
when Dependency
@deps << dep
when Requirement
@requirements << dep
end
dep
end
def fetch(spec)
CACHE.fetch(cache_key(spec)) { |key| CACHE[key] = build(spec) }
end
def cache_key(spec)
if Resource === spec && spec.download_strategy == CurlDownloadStrategy
File.extname(spec.url)
else
spec
end
end
def build(spec)
spec, tags = case spec
when Hash then destructure_spec_hash(spec)
else spec
end
parse_spec(spec, Array(tags))
end
private
def destructure_spec_hash(spec)
spec.each { |o| return o }
end
def parse_spec(spec, tags)
case spec
when String
parse_string_spec(spec, tags)
when Resource
resource_dep(spec, tags)
when Symbol
parse_symbol_spec(spec, tags)
when Requirement, Dependency
spec
when Class
parse_class_spec(spec, tags)
else
raise TypeError, "Unsupported type #{spec.class.name} for #{spec.inspect}"
end
end
def parse_string_spec(spec, tags)
if tags.empty?
Dependency.new(spec, tags)
elsif (tag = tags.first) && LANGUAGE_MODULES.include?(tag)
LanguageModuleDependency.new(tag, spec, tags[1])
elsif HOMEBREW_TAP_FORMULA_REGEX === spec
TapDependency.new(spec, tags)
else
Dependency.new(spec, tags)
end
end
def parse_symbol_spec(spec, tags)
case spec
when :autoconf, :automake, :bsdmake, :libtool
# Xcode no longer provides autotools or some other build tools
autotools_dep(spec, tags)
when :x11 then X11Dependency.new(spec.to_s, tags)
when :xcode then XcodeDependency.new(tags)
when :macos then MinimumMacOSRequirement.new(tags)
when :mysql then MysqlDependency.new(tags)
when :postgresql then PostgresqlDependency.new(tags)
when :fortran then FortranDependency.new(tags)
when :mpi then MPIDependency.new(*tags)
when :tex then TeXDependency.new(tags)
when :arch then ArchRequirement.new(tags)
when :hg then MercurialDependency.new(tags)
# python2 is deprecated
when :python, :python2 then PythonDependency.new(tags)
when :python3 then Python3Dependency.new(tags)
when :java then JavaDependency.new(tags)
when :osxfuse then OsxfuseDependency.new(tags)
# Tiger's ld is too old to properly link some software
when :ld64 then LD64Dependency.new if MacOS.version < :leopard
when :ant then ant_dep(spec, tags)
when :clt # deprecated
when :cairo, :fontconfig, :freetype, :libpng, :pixman # deprecated
Dependency.new(spec.to_s, tags)
when :libltdl # deprecated
tags << :run
Dependency.new("libtool", tags)
else
raise ArgumentError, "Unsupported special dependency #{spec.inspect}"
end
end
def parse_class_spec(spec, tags)
if spec < Requirement
spec.new(tags)
else
raise TypeError, "#{spec.inspect} is not a Requirement subclass"
end
end
def autotools_dep(spec, tags)
tags << :build unless tags.include? :run
Dependency.new(spec.to_s, tags)
end
def ant_dep(spec, tags)
if MacOS.version >= :mavericks
Dependency.new(spec.to_s, tags)
end
end
def resource_dep(spec, tags)
tags << :build
strategy = spec.download_strategy
case
when strategy <= CurlDownloadStrategy
parse_url_spec(spec.url, tags)
when strategy <= GitDownloadStrategy
GitDependency.new(tags)
when strategy <= MercurialDownloadStrategy
MercurialDependency.new(tags)
when strategy <= FossilDownloadStrategy
Dependency.new("fossil", tags)
when strategy <= BazaarDownloadStrategy
Dependency.new("bazaar", tags)
when strategy <= CVSDownloadStrategy
Dependency.new("cvs", tags) if MacOS.version >= :mavericks || !MacOS::Xcode.provides_cvs?
when strategy < AbstractDownloadStrategy
# allow unknown strategies to pass through
else
raise TypeError,
"#{strategy.inspect} is not an AbstractDownloadStrategy subclass"
end
end
def parse_url_spec(url, tags)
case File.extname(url)
when '.xz' then Dependency.new('xz', tags)
when '.lz' then Dependency.new('lzip', tags)
when '.rar' then Dependency.new('unrar', tags)
when '.7z' then Dependency.new('p7zip', tags)
end
end
end

View File

@ -0,0 +1,835 @@
require 'utils/json'
class AbstractDownloadStrategy
attr_reader :name, :resource
def initialize name, resource
@name = name
@resource = resource
@url = resource.url
end
def expand_safe_system_args args
args = args.dup
args.each_with_index do |arg, ii|
if arg.is_a? Hash
unless ARGV.verbose?
args[ii] = arg[:quiet_flag]
else
args.delete_at ii
end
return args
end
end
# 2 as default because commands are eg. svn up, git pull
args.insert(2, '-q') unless ARGV.verbose?
args
end
def quiet_safe_system *args
safe_system(*expand_safe_system_args(args))
end
# All download strategies are expected to implement these methods
def fetch; end
def stage; end
def cached_location; end
def clear_cache; end
end
class VCSDownloadStrategy < AbstractDownloadStrategy
REF_TYPES = [:branch, :revision, :revisions, :tag].freeze
def initialize name, resource
super
@ref_type, @ref = extract_ref(resource.specs)
@clone = HOMEBREW_CACHE.join(cache_filename)
end
def extract_ref(specs)
key = REF_TYPES.find { |type| specs.key?(type) }
return key, specs[key]
end
def cache_filename
"#{name}--#{cache_tag}"
end
def cache_tag
"__UNKNOWN__"
end
def cached_location
@clone
end
def clear_cache
cached_location.rmtree if cached_location.exist?
end
end
class CurlDownloadStrategy < AbstractDownloadStrategy
def mirrors
@mirrors ||= resource.mirrors.dup
end
def tarball_path
@tarball_path ||= Pathname.new("#{HOMEBREW_CACHE}/#{name}-#{resource.version}#{ext}")
end
def temporary_path
@temporary_path ||= Pathname.new("#{tarball_path}.incomplete")
end
def cached_location
tarball_path
end
def clear_cache
[cached_location, temporary_path].each { |f| f.unlink if f.exist? }
end
def downloaded_size
temporary_path.size? or 0
end
# Private method, can be overridden if needed.
def _fetch
curl @url, '-C', downloaded_size, '-o', temporary_path
end
def fetch
ohai "Downloading #{@url}"
unless tarball_path.exist?
had_incomplete_download = temporary_path.exist?
begin
_fetch
rescue ErrorDuringExecution
# 33 == range not supported
# try wiping the incomplete download and retrying once
if $?.exitstatus == 33 && had_incomplete_download
ohai "Trying a full download"
temporary_path.unlink
had_incomplete_download = false
retry
else
if @url =~ %r[^file://]
msg = "File does not exist: #{@url.sub(%r[^file://], "")}"
else
msg = "Download failed: #{@url}"
end
raise CurlDownloadStrategyError, msg
end
end
ignore_interrupts { temporary_path.rename(tarball_path) }
else
puts "Already downloaded: #{tarball_path}"
end
rescue CurlDownloadStrategyError
raise if mirrors.empty?
puts "Trying a mirror..."
@url = mirrors.shift
retry
else
tarball_path
end
# gunzip and bunzip2 write the output file in the same directory as the input
# file regardless of the current working directory, so we need to write it to
# the correct location ourselves.
def buffered_write(tool)
target = File.basename(basename_without_params, tarball_path.extname)
Utils.popen_read(tool, "-f", tarball_path.to_s, "-c") do |pipe|
File.open(target, "wb") do |f|
buf = ""
f.write(buf) while pipe.read(1024, buf)
end
end
end
def stage
case tarball_path.compression_type
when :zip
with_system_path { quiet_safe_system 'unzip', {:quiet_flag => '-qq'}, tarball_path }
chdir
when :gzip_only
with_system_path { buffered_write("gunzip") }
when :bzip2_only
with_system_path { buffered_write("bunzip2") }
when :gzip, :bzip2, :compress, :tar
# Assume these are also tarred
# TODO check if it's really a tar archive
with_system_path { safe_system 'tar', 'xf', tarball_path }
chdir
when :xz
with_system_path { safe_system "#{xzpath} -dc \"#{tarball_path}\" | tar xf -" }
chdir
when :lzip
with_system_path { safe_system "#{lzippath} -dc \"#{tarball_path}\" | tar xf -" }
chdir
when :xar
safe_system "/usr/bin/xar", "-xf", tarball_path
when :rar
quiet_safe_system 'unrar', 'x', {:quiet_flag => '-inul'}, tarball_path
when :p7zip
safe_system '7zr', 'x', tarball_path
else
FileUtils.cp tarball_path, basename_without_params
end
end
private
def curl(*args)
args << '--connect-timeout' << '5' unless mirrors.empty?
super
end
def xzpath
"#{HOMEBREW_PREFIX}/opt/xz/bin/xz"
end
def lzippath
"#{HOMEBREW_PREFIX}/opt/lzip/bin/lzip"
end
def chdir
entries = Dir['*']
case entries.length
when 0 then raise "Empty archive"
when 1 then Dir.chdir entries.first rescue nil
end
end
def basename_without_params
# Strip any ?thing=wad out of .c?thing=wad style extensions
File.basename(@url)[/[^?]+/]
end
def ext
# We need a Pathname because we've monkeypatched extname to support double
# extensions (e.g. tar.gz).
# We can't use basename_without_params, because given a URL like
# http://example.com/download.php?file=foo-1.0.tar.gz
# the extension we want is ".tar.gz", not ".php".
Pathname.new(@url).extname[/[^?]+/]
end
end
# Detect and download from Apache Mirror
class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy
def apache_mirrors
rd, wr = IO.pipe
buf = ""
pid = fork do
rd.close
$stdout.reopen(wr)
$stderr.reopen(wr)
curl "#{@url}&asjson=1"
end
wr.close
rd.readline if ARGV.verbose? # Remove Homebrew output
buf << rd.read until rd.eof?
rd.close
Process.wait(pid)
buf
end
def _fetch
return super if @tried_apache_mirror
@tried_apache_mirror = true
mirrors = Utils::JSON.load(apache_mirrors)
@url = mirrors.fetch('preferred') + mirrors.fetch('path_info')
ohai "Best Mirror #{@url}"
super
rescue IndexError, Utils::JSON::Error
raise CurlDownloadStrategyError, "Couldn't determine mirror, try again later."
end
end
# Download via an HTTP POST.
# Query parameters on the URL are converted into POST parameters
class CurlPostDownloadStrategy < CurlDownloadStrategy
def _fetch
base_url,data = @url.split('?')
curl base_url, '-d', data, '-C', downloaded_size, '-o', temporary_path
end
end
# Download from an SSL3-only host.
class CurlSSL3DownloadStrategy < CurlDownloadStrategy
def _fetch
curl @url, '-3', '-C', downloaded_size, '-o', temporary_path
end
end
# Use this strategy to download but not unzip a file.
# Useful for installing jars.
class NoUnzipCurlDownloadStrategy < CurlDownloadStrategy
def stage
FileUtils.cp tarball_path, basename_without_params
end
end
# This strategy is provided for use with sites that only provide HTTPS and
# also have a broken cert. Try not to need this, as we probably won't accept
# the formula.
class CurlUnsafeDownloadStrategy < CurlDownloadStrategy
def _fetch
curl @url, '--insecure', '-C', downloaded_size, '-o', temporary_path
end
end
# This strategy extracts our binary packages.
class CurlBottleDownloadStrategy < CurlDownloadStrategy
def initialize name, resource
super
mirror = ENV['HOMEBREW_SOURCEFORGE_MIRROR']
@url = "#{@url}?use_mirror=#{mirror}" if mirror
end
def stage
ohai "Pouring #{tarball_path.basename}"
super
end
end
# This strategy extracts local binary packages.
class LocalBottleDownloadStrategy < CurlDownloadStrategy
def initialize formula
super formula.name, formula.active_spec
@tarball_path = formula.local_bottle_path
end
def stage
ohai "Pouring #{tarball_path.basename}"
super
end
end
# S3DownloadStrategy downloads tarballs from AWS S3.
# To use it, add ":using => S3DownloadStrategy" to the URL section of your
# formula. This download strategy uses AWS access tokens (in the
# environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)
# to sign the request. This strategy is good in a corporate setting,
# because it lets you use a private S3 bucket as a repo for internal
# distribution. (It will work for public buckets as well.)
class S3DownloadStrategy < CurlDownloadStrategy
def _fetch
# Put the aws gem requirement here (vs top of file) so it's only
# a dependency of S3 users, not all Homebrew users
require 'rubygems'
begin
require 'aws-sdk'
rescue LoadError
onoe "Install the aws-sdk gem into the gem repo used by brew."
raise
end
if @url !~ %r[^https?://+([^.]+).s3.amazonaws.com/+(.+)$] then
raise "Bad S3 URL: " + @url
end
(bucket,key) = $1,$2
obj = AWS::S3.new().buckets[bucket].objects[key]
begin
s3url = obj.url_for(:get)
rescue AWS::Errors::MissingCredentialsError
ohai "AWS credentials missing, trying public URL instead."
s3url = obj.public_url
end
curl s3url, '-C', downloaded_size, '-o', temporary_path
end
end
class SubversionDownloadStrategy < VCSDownloadStrategy
def cache_tag
resource.version.head? ? "svn-HEAD" : "svn"
end
def repo_valid?
@clone.join(".svn").directory?
end
def repo_url
`svn info '#{@clone}' 2>/dev/null`.strip[/^URL: (.+)$/, 1]
end
def fetch
@url = @url.sub(/^svn\+/, '') if @url =~ %r[^svn\+http://]
ohai "Checking out #{@url}"
clear_cache unless @url.chomp("/") == repo_url or quiet_system 'svn', 'switch', @url, @clone
if @clone.exist? and not repo_valid?
puts "Removing invalid SVN repo from cache"
clear_cache
end
case @ref_type
when :revision
fetch_repo @clone, @url, @ref
when :revisions
# nil is OK for main_revision, as fetch_repo will then get latest
main_revision = @ref[:trunk]
fetch_repo @clone, @url, main_revision, true
get_externals do |external_name, external_url|
fetch_repo @clone+external_name, external_url, @ref[external_name], true
end
else
fetch_repo @clone, @url
end
end
def stage
quiet_safe_system 'svn', 'export', '--force', @clone, Dir.pwd
end
def shell_quote str
# Oh god escaping shell args.
# See http://notetoself.vrensk.com/2008/08/escaping-single-quotes-in-ruby-harder-than-expected/
str.gsub(/\\|'/) { |c| "\\#{c}" }
end
def get_externals
`svn propget svn:externals '#{shell_quote(@url)}'`.chomp.each_line do |line|
name, url = line.split(/\s+/)
yield name, url
end
end
def fetch_repo target, url, revision=nil, ignore_externals=false
# Use "svn up" when the repository already exists locally.
# This saves on bandwidth and will have a similar effect to verifying the
# cache as it will make any changes to get the right revision.
svncommand = target.directory? ? 'up' : 'checkout'
args = ['svn', svncommand]
# SVN shipped with XCode 3.1.4 can't force a checkout.
args << '--force' unless MacOS.version == :leopard
args << url unless target.directory?
args << target
args << '-r' << revision if revision
args << '--ignore-externals' if ignore_externals
quiet_safe_system(*args)
end
end
# Require a newer version of Subversion than 1.4.x (Leopard-provided version)
class StrictSubversionDownloadStrategy < SubversionDownloadStrategy
def find_svn
exe = `svn -print-path`
`#{exe} --version` =~ /version (\d+\.\d+(\.\d+)*)/
svn_version = $1
version_tuple=svn_version.split(".").collect {|v|Integer(v)}
if version_tuple[0] == 1 and version_tuple[1] <= 4
onoe "Detected Subversion (#{exe}, version #{svn_version}) is too old."
puts "Subversion 1.4.x will not export externals correctly for this formula."
puts "You must either `brew install subversion` or set HOMEBREW_SVN to the path"
puts "of a newer svn binary."
end
return exe
end
end
# Download from SVN servers with invalid or self-signed certs
class UnsafeSubversionDownloadStrategy < SubversionDownloadStrategy
def fetch_repo target, url, revision=nil, ignore_externals=false
# Use "svn up" when the repository already exists locally.
# This saves on bandwidth and will have a similar effect to verifying the
# cache as it will make any changes to get the right revision.
svncommand = target.directory? ? 'up' : 'checkout'
args = ['svn', svncommand, '--non-interactive', '--trust-server-cert', '--force']
args << url unless target.directory?
args << target
args << '-r' << revision if revision
args << '--ignore-externals' if ignore_externals
quiet_safe_system(*args)
end
end
class GitDownloadStrategy < VCSDownloadStrategy
SHALLOW_CLONE_WHITELIST = [
%r{git://},
%r{https://github\.com},
%r{http://git\.sv\.gnu\.org},
%r{http://llvm\.org},
]
def initialize name, resource
super
@shallow = resource.specs.fetch(:shallow) { true }
end
def cache_tag; "git" end
def fetch
ohai "Cloning #@url"
if @clone.exist? && repo_valid?
puts "Updating #@clone"
@clone.cd do
config_repo
update_repo
checkout
reset
update_submodules if submodules?
end
elsif @clone.exist?
puts "Removing invalid .git repo from cache"
clear_cache
clone_repo
else
clone_repo
end
end
def stage
dst = Dir.getwd
@clone.cd do
if @ref_type and @ref
ohai "Checking out #@ref_type #@ref"
else
reset
end
# http://stackoverflow.com/questions/160608/how-to-do-a-git-export-like-svn-export
safe_system 'git', 'checkout-index', '-a', '-f', "--prefix=#{dst}/"
checkout_submodules(dst) if submodules?
end
end
private
def shallow_clone?
@shallow && support_depth?
end
def support_depth?
@ref_type != :revision && SHALLOW_CLONE_WHITELIST.any? { |rx| rx === @url }
end
def git_dir
@clone.join(".git")
end
def has_ref?
quiet_system 'git', '--git-dir', git_dir, 'rev-parse', '-q', '--verify', "#{@ref}^{commit}"
end
def repo_valid?
quiet_system "git", "--git-dir", git_dir, "status", "-s"
end
def submodules?
@clone.join(".gitmodules").exist?
end
def clone_args
args = %w{clone}
args << '--depth' << '1' if shallow_clone?
case @ref_type
when :branch, :tag then args << '--branch' << @ref
end
args << @url << @clone
end
def refspec
case @ref_type
when :branch then "+refs/heads/#@ref:refs/remotes/origin/#@ref"
when :tag then "+refs/tags/#@ref:refs/tags/#@ref"
else "+refs/heads/master:refs/remotes/origin/master"
end
end
def config_repo
safe_system 'git', 'config', 'remote.origin.url', @url
safe_system 'git', 'config', 'remote.origin.fetch', refspec
end
def update_repo
# Branches always need updated. The has_ref? check will only work if a ref
# has been specified; if there isn't one we always want an update.
if @ref_type == :branch || !@ref || !has_ref?
quiet_safe_system 'git', 'fetch', 'origin'
end
end
def clone_repo
safe_system 'git', *clone_args
@clone.cd { update_submodules } if submodules?
end
def checkout_args
ref = case @ref_type
when :branch, :tag, :revision then @ref
else `git symbolic-ref refs/remotes/origin/HEAD`.strip.split("/").last
end
%W{checkout -f #{ref}}
end
def checkout
quiet_safe_system 'git', *checkout_args
end
def reset_args
ref = case @ref_type
when :branch then "origin/#@ref"
when :revision, :tag then @ref
else "origin/HEAD"
end
%W{reset --hard #{ref}}
end
def reset
quiet_safe_system 'git', *reset_args
end
def update_submodules
safe_system 'git', 'submodule', 'update', '--init', '--recursive'
end
def checkout_submodules(dst)
escaped_clone_path = @clone.to_s.gsub(/\//, '\/')
sub_cmd = "git checkout-index -a -f --prefix=#{dst}/${toplevel/#{escaped_clone_path}/}/$path/"
safe_system 'git', 'submodule', '--quiet', 'foreach', '--recursive', sub_cmd
end
end
class CVSDownloadStrategy < VCSDownloadStrategy
def cvspath
@path ||= %W[
/usr/bin/cvs
#{HOMEBREW_PREFIX}/bin/cvs
#{HOMEBREW_PREFIX}/opt/cvs/bin/cvs
#{which("cvs")}
].find { |p| File.executable? p }
end
def cache_tag; "cvs" end
def fetch
ohai "Checking out #{@url}"
# URL of cvs cvs://:pserver:anoncvs@www.gccxml.org:/cvsroot/GCC_XML:gccxml
# will become:
# cvs -d :pserver:anoncvs@www.gccxml.org:/cvsroot/GCC_XML login
# cvs -d :pserver:anoncvs@www.gccxml.org:/cvsroot/GCC_XML co gccxml
mod, url = split_url(@url)
unless @clone.exist?
HOMEBREW_CACHE.cd do
safe_system cvspath, '-d', url, 'login'
safe_system cvspath, '-d', url, 'checkout', '-d', cache_filename, mod
end
else
puts "Updating #{@clone}"
@clone.cd { safe_system cvspath, 'up' }
end
end
def stage
FileUtils.cp_r Dir[@clone+"{.}"], Dir.pwd
end
private
def split_url(in_url)
parts=in_url.sub(%r[^cvs://], '').split(/:/)
mod=parts.pop
url=parts.join(':')
[ mod, url ]
end
end
class MercurialDownloadStrategy < VCSDownloadStrategy
def cache_tag; "hg" end
def hgpath
# Note: #{HOMEBREW_PREFIX}/share/python/hg is deprecated
@path ||= %W[
#{which("hg")}
#{HOMEBREW_PREFIX}/bin/hg
#{HOMEBREW_PREFIX}/opt/mercurial/bin/hg
#{HOMEBREW_PREFIX}/share/python/hg
].find { |p| File.executable? p }
end
def fetch
ohai "Cloning #{@url}"
if @clone.exist? && repo_valid?
puts "Updating #{@clone}"
@clone.cd { quiet_safe_system hgpath, 'pull', '--update' }
elsif @clone.exist?
puts "Removing invalid hg repo from cache"
clear_cache
clone_repo
else
clone_repo
end
end
def repo_valid?
@clone.join(".hg").directory?
end
def clone_repo
url = @url.sub(%r[^hg://], '')
safe_system hgpath, 'clone', url, @clone
end
def stage
dst = Dir.getwd
@clone.cd do
if @ref_type and @ref
ohai "Checking out #{@ref_type} #{@ref}"
safe_system hgpath, 'archive', '--subrepos', '-y', '-r', @ref, '-t', 'files', dst
else
safe_system hgpath, 'archive', '--subrepos', '-y', '-t', 'files', dst
end
end
end
end
class BazaarDownloadStrategy < VCSDownloadStrategy
def cache_tag; "bzr" end
def bzrpath
@path ||= %W[
#{which("bzr")}
#{HOMEBREW_PREFIX}/bin/bzr
].find { |p| File.executable? p }
end
def repo_valid?
@clone.join(".bzr").directory?
end
def fetch
ohai "Cloning #{@url}"
if @clone.exist? && repo_valid?
puts "Updating #{@clone}"
@clone.cd { safe_system bzrpath, 'update' }
elsif @clone.exist?
puts "Removing invalid bzr repo from cache"
clear_cache
clone_repo
else
clone_repo
end
end
def clone_repo
url = @url.sub(%r[^bzr://], '')
# 'lightweight' means history-less
safe_system bzrpath, 'checkout', '--lightweight', url, @clone
end
def stage
# FIXME: The export command doesn't work on checkouts
# See https://bugs.launchpad.net/bzr/+bug/897511
FileUtils.cp_r Dir[@clone+"{.}"], Dir.pwd
FileUtils.rm_r ".bzr"
end
end
class FossilDownloadStrategy < VCSDownloadStrategy
def cache_tag; "fossil" end
def fossilpath
@path ||= %W[
#{which("fossil")}
#{HOMEBREW_PREFIX}/bin/fossil
].find { |p| File.executable? p }
end
def fetch
ohai "Cloning #{@url}"
unless @clone.exist?
url=@url.sub(%r[^fossil://], '')
safe_system fossilpath, 'clone', url, @clone
else
puts "Updating #{@clone}"
safe_system fossilpath, 'pull', '-R', @clone
end
end
def stage
# TODO: The 'open' and 'checkout' commands are very noisy and have no '-q' option.
safe_system fossilpath, 'open', @clone
if @ref_type and @ref
ohai "Checking out #{@ref_type} #{@ref}"
safe_system fossilpath, 'checkout', @ref
end
end
end
class DownloadStrategyDetector
def self.detect(url, strategy=nil)
if strategy.nil?
detect_from_url(url)
elsif Class === strategy && strategy < AbstractDownloadStrategy
strategy
elsif Symbol === strategy
detect_from_symbol(strategy)
else
raise TypeError,
"Unknown download strategy specification #{strategy.inspect}"
end
end
def self.detect_from_url(url)
case url
when %r[^https?://.+\.git$], %r[^git://]
GitDownloadStrategy
when %r[^http://www\.apache\.org/dyn/closer\.cgi]
CurlApacheMirrorDownloadStrategy
when %r[^https?://(.+?\.)?googlecode\.com/svn], %r[^https?://svn\.], %r[^svn://], %r[^https?://(.+?\.)?sourceforge\.net/svnroot/]
SubversionDownloadStrategy
when %r[^cvs://]
CVSDownloadStrategy
when %r[^https?://(.+?\.)?googlecode\.com/hg]
MercurialDownloadStrategy
when %r[^hg://]
MercurialDownloadStrategy
when %r[^bzr://]
BazaarDownloadStrategy
when %r[^fossil://]
FossilDownloadStrategy
when %r[^http://svn\.apache\.org/repos/], %r[^svn\+http://]
SubversionDownloadStrategy
when %r[^https?://(.+?\.)?sourceforge\.net/hgweb/]
MercurialDownloadStrategy
else
CurlDownloadStrategy
end
end
def self.detect_from_symbol(symbol)
case symbol
when :hg then MercurialDownloadStrategy
when :nounzip then NoUnzipCurlDownloadStrategy
when :git then GitDownloadStrategy
when :bzr then BazaarDownloadStrategy
when :svn then SubversionDownloadStrategy
when :curl then CurlDownloadStrategy
when :ssl3 then CurlSSL3DownloadStrategy
when :cvs then CVSDownloadStrategy
when :post then CurlPostDownloadStrategy
else
raise "Unknown download strategy #{strategy} was requested."
end
end
end

View File

@ -0,0 +1,255 @@
class UsageError < RuntimeError; end
class FormulaUnspecifiedError < UsageError; end
class KegUnspecifiedError < UsageError; end
class MultipleVersionsInstalledError < RuntimeError
attr_reader :name
def initialize name
@name = name
super "#{name} has multiple installed versions"
end
end
class NotAKegError < RuntimeError; end
class NoSuchKegError < RuntimeError
attr_reader :name
def initialize name
@name = name
super "No such keg: #{HOMEBREW_CELLAR}/#{name}"
end
end
class FormulaValidationError < StandardError
attr_reader :attr
def initialize(attr, value)
@attr = attr
msg = "invalid attribute: #{attr}"
msg << " (#{value.inspect})" unless value.empty?
super msg
end
end
class FormulaSpecificationError < StandardError; end
class FormulaUnavailableError < RuntimeError
attr_reader :name
attr_accessor :dependent
def initialize name
@name = name
end
def dependent_s
"(dependency of #{dependent})" if dependent and dependent != name
end
def to_s
"No available formula for #{name} #{dependent_s}"
end
end
class TapFormulaUnavailableError < FormulaUnavailableError
attr_reader :user, :repo, :shortname
def initialize name
super
@user, @repo, @shortname = name.split("/", 3)
end
def to_s; <<-EOS.undent
No available formula for #{shortname} #{dependent_s}
Please tap it and then try again: brew tap #{user}/#{repo}
EOS
end
end
class OperationInProgressError < RuntimeError
def initialize name
message = <<-EOS.undent
Operation already in progress for #{name}
Another active Homebrew process is already using #{name}.
Please wait for it to finish or terminate it to continue.
EOS
super message
end
end
class CannotInstallFormulaError < RuntimeError; end
class FormulaAlreadyInstalledError < RuntimeError; end
class FormulaInstallationAlreadyAttemptedError < RuntimeError
def initialize(formula)
super "Formula installation already attempted: #{formula.name}"
end
end
class UnsatisfiedRequirements < RuntimeError
def initialize(reqs)
if reqs.length == 1
super "An unsatisfied requirement failed this build."
else
super "Unsatisified requirements failed this build."
end
end
end
class FormulaConflictError < RuntimeError
attr_reader :formula, :conflicts
def initialize(formula, conflicts)
@formula = formula
@conflicts = conflicts
super message
end
def conflict_message(conflict)
message = []
message << " #{conflict.name}"
message << ": because #{conflict.reason}" if conflict.reason
message.join
end
def message
message = []
message << "Cannot install #{formula.name} because conflicting formulae are installed.\n"
message.concat conflicts.map { |c| conflict_message(c) } << ""
message << <<-EOS.undent
Please `brew unlink #{conflicts.map(&:name)*' '}` before continuing.
Unlinking removes a formula's symlinks from #{HOMEBREW_PREFIX}. You can
link the formula again after the install finishes. You can --force this
install, but the build may fail or cause obscure side-effects in the
resulting software.
EOS
message.join("\n")
end
end
class BuildError < RuntimeError
attr_reader :formula, :env
def initialize(formula, cmd, args, env)
@formula = formula
@env = env
args = args.map{ |arg| arg.to_s.gsub " ", "\\ " }.join(" ")
super "Failed executing: #{cmd} #{args}"
end
def issues
@issues ||= fetch_issues
end
def fetch_issues
GitHub.issues_for_formula(formula.name)
rescue GitHub::RateLimitExceededError => e
opoo e.message
[]
end
def dump
if not ARGV.verbose?
puts
puts "#{Tty.red}READ THIS#{Tty.reset}: #{Tty.em}#{OS::ISSUES_URL}#{Tty.reset}"
if formula.tap?
puts "If reporting this issue please do so at (not Homebrew/homebrew):"
puts " https://github.com/#{formula.tap}/issues"
end
else
require 'cmd/config'
require 'cmd/--env'
unless formula.core_formula?
ohai "Formula"
puts "Tap: #{formula.tap}"
puts "Path: #{formula.path}"
end
ohai "Configuration"
Homebrew.dump_build_config
ohai "ENV"
Homebrew.dump_build_env(env)
puts
onoe "#{formula.name} #{formula.version} did not build"
unless (logs = Dir["#{HOMEBREW_LOGS}/#{formula.name}/*"]).empty?
puts "Logs:"
puts logs.map{|fn| " #{fn}"}.join("\n")
end
end
puts
unless RUBY_VERSION < "1.8.7" || issues.empty?
puts "These open issues may also help:"
puts issues.map{ |i| "#{i['title']} (#{i['html_url']})" }.join("\n")
end
end
end
# raised by CompilerSelector if the formula fails with all of
# the compilers available on the user's system
class CompilerSelectionError < RuntimeError
def initialize(formula)
super <<-EOS.undent
#{formula.name} cannot be built with any available compilers.
To install this formula, you may need to:
brew install gcc
EOS
end
end
# Raised in Resource.fetch
class DownloadError < RuntimeError
def initialize(resource, e)
super <<-EOS.undent
Failed to download resource #{resource.download_name.inspect}
#{e.message}
EOS
end
end
# raised in CurlDownloadStrategy.fetch
class CurlDownloadStrategyError < RuntimeError; end
# raised by safe_system in utils.rb
class ErrorDuringExecution < RuntimeError
def initialize(cmd, args=[])
args = args.map { |a| a.to_s.gsub " ", "\\ " }.join(" ")
super "Failure while executing: #{cmd} #{args}"
end
end
# raised by Pathname#verify_checksum when "expected" is nil or empty
class ChecksumMissingError < ArgumentError; end
# raised by Pathname#verify_checksum when verification fails
class ChecksumMismatchError < RuntimeError
attr_reader :expected, :hash_type
def initialize fn, expected, actual
@expected = expected
@hash_type = expected.hash_type.to_s.upcase
super <<-EOS.undent
#{@hash_type} mismatch
Expected: #{expected}
Actual: #{actual}
Archive: #{fn}
To retry an incomplete download, remove the file above.
EOS
end
end
class ResourceMissingError < ArgumentError
def initialize(formula, resource)
super "#{formula.name} does not define resource #{resource.inspect}"
end
end
class DuplicateResourceError < ArgumentError
def initialize(resource)
super "Resource #{resource.inspect} is defined more than once"
end
end

View File

@ -0,0 +1,186 @@
module HomebrewArgvExtension
def named
@named ||= self - options_only
end
def options_only
select { |arg| arg.start_with?("-") }
end
def flags_only
select { |arg| arg.start_with?("--") }
end
def formulae
require "formula"
@formulae ||= downcased_unique_named.map { |name| Formulary.factory(name, spec) }
end
def kegs
require 'keg'
require 'formula'
@kegs ||= downcased_unique_named.collect do |name|
canonical_name = Formulary.canonical_name(name)
rack = HOMEBREW_CELLAR/canonical_name
dirs = rack.directory? ? rack.subdirs : []
raise NoSuchKegError.new(canonical_name) if dirs.empty?
linked_keg_ref = HOMEBREW_LIBRARY.join("LinkedKegs", canonical_name)
opt_prefix = HOMEBREW_PREFIX.join("opt", canonical_name)
begin
if opt_prefix.symlink? && opt_prefix.directory?
Keg.new(opt_prefix.resolved_path)
elsif linked_keg_ref.symlink? && linked_keg_ref.directory?
Keg.new(linked_keg_ref.resolved_path)
elsif dirs.length == 1
Keg.new(dirs.first)
elsif (prefix = Formulary.factory(canonical_name).prefix).directory?
Keg.new(prefix)
else
raise MultipleVersionsInstalledError.new(canonical_name)
end
rescue FormulaUnavailableError
raise <<-EOS.undent
Multiple kegs installed to #{rack}
However we don't know which one you refer to.
Please delete (with rm -rf!) all but one and then try again.
EOS
end
end
end
# self documenting perhaps?
def include? arg
@n=index arg
end
def next
at @n+1 or raise UsageError
end
def value arg
arg = find {|o| o =~ /--#{arg}=(.+)/}
$1 if arg
end
def force?
flag? '--force'
end
def verbose?
flag? '--verbose' or !ENV['VERBOSE'].nil? or !ENV['HOMEBREW_VERBOSE'].nil?
end
def debug?
flag? '--debug' or !ENV['HOMEBREW_DEBUG'].nil?
end
def quieter?
flag? '--quieter'
end
def interactive?
flag? '--interactive'
end
def one?
flag? '--1'
end
def dry_run?
include?('--dry-run') || switch?('n')
end
def homebrew_developer?
include? '--homebrew-developer' or !ENV['HOMEBREW_DEVELOPER'].nil?
end
def ignore_deps?
include? '--ignore-dependencies'
end
def only_deps?
include? '--only-dependencies'
end
def json
value 'json'
end
def build_head?
include? '--HEAD'
end
def build_devel?
include? '--devel'
end
def build_stable?
not (build_head? or build_devel?)
end
def build_universal?
include? '--universal'
end
# Request a 32-bit only build.
# This is needed for some use-cases though we prefer to build Universal
# when a 32-bit version is needed.
def build_32_bit?
include? '--32-bit'
end
def build_bottle?
include? '--build-bottle' or !ENV['HOMEBREW_BUILD_BOTTLE'].nil?
end
def bottle_arch
arch = value 'bottle-arch'
arch.to_sym if arch
end
def build_from_source?
switch?("s") || include?("--build-from-source") || !!ENV["HOMEBREW_BUILD_FROM_SOURCE"]
end
def flag? flag
options_only.include?(flag) || switch?(flag[2, 1])
end
def force_bottle?
include? '--force-bottle'
end
# eg. `foo -ns -i --bar` has three switches, n, s and i
def switch? char
return false if char.length > 1
options_only.any? { |arg| arg[1, 1] != "-" && arg.include?(char) }
end
def usage
require 'cmd/help'
Homebrew.help_s
end
def cc
value 'cc'
end
def env
value 'env'
end
private
def spec
if include?("--HEAD")
:head
elsif include?("--devel")
:devel
else
:stable
end
end
def downcased_unique_named
# Only lowercase names, not paths or URLs
@downcased_unique_named ||= named.map do |arg|
arg.include?("/") ? arg : arg.downcase
end.uniq
end
end

View File

@ -0,0 +1,31 @@
require 'hardware'
require 'extend/ENV/shared'
require 'extend/ENV/std'
require 'extend/ENV/super'
def superenv?
Superenv.bin && ARGV.env != "std"
end
module EnvActivation
def activate_extensions!
if superenv?
extend(Superenv)
else
extend(Stdenv)
end
end
def with_build_environment
old_env = to_hash.dup
tmp_env = to_hash.dup.extend(EnvActivation)
tmp_env.activate_extensions!
tmp_env.setup_build_environment
replace(tmp_env)
yield
ensure
replace(old_env)
end
end
ENV.extend(EnvActivation)

View File

@ -0,0 +1,278 @@
require "formula"
require "compilers"
module SharedEnvExtension
include CompilerConstants
CC_FLAG_VARS = %w{CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS}
FC_FLAG_VARS = %w{FCFLAGS FFLAGS}
COMPILER_SYMBOL_MAP = {
"gcc-4.0" => :gcc_4_0,
"gcc-4.2" => :gcc,
"llvm-gcc" => :llvm,
"clang" => :clang,
}
COMPILERS = COMPILER_SYMBOL_MAP.values +
GNU_GCC_VERSIONS.map { |n| "gcc-4.#{n}" }
SANITIZED_VARS = %w[
CDPATH GREP_OPTIONS CLICOLOR_FORCE
CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH OBJC_INCLUDE_PATH
CC CXX OBJC OBJCXX CPP MAKE LD LDSHARED
CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS LDFLAGS CPPFLAGS
MACOSX_DEPLOYMENT_TARGET SDKROOT DEVELOPER_DIR
CMAKE_PREFIX_PATH CMAKE_INCLUDE_PATH CMAKE_FRAMEWORK_PATH
GOBIN
]
def setup_build_environment(formula=nil)
@formula = formula
reset
end
def reset
SANITIZED_VARS.each { |k| delete(k) }
end
def remove_cc_etc
keys = %w{CC CXX OBJC OBJCXX LD CPP CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS LDFLAGS CPPFLAGS}
removed = Hash[*keys.map{ |key| [key, self[key]] }.flatten]
keys.each do |key|
delete(key)
end
removed
end
def append_to_cflags newflags
append(CC_FLAG_VARS, newflags)
end
def remove_from_cflags val
remove CC_FLAG_VARS, val
end
def append keys, value, separator = ' '
value = value.to_s
Array(keys).each do |key|
old = self[key]
if old.nil? || old.empty?
self[key] = value
else
self[key] += separator + value
end
end
end
def prepend keys, value, separator = ' '
value = value.to_s
Array(keys).each do |key|
old = self[key]
if old.nil? || old.empty?
self[key] = value
else
self[key] = value + separator + old
end
end
end
def append_path key, path
append key, path, File::PATH_SEPARATOR if File.directory? path
end
def prepend_path key, path
prepend key, path, File::PATH_SEPARATOR if File.directory? path
end
def prepend_create_path key, path
path = Pathname.new(path) unless path.is_a? Pathname
path.mkpath
prepend_path key, path
end
def remove keys, value
Array(keys).each do |key|
next unless self[key]
self[key] = self[key].sub(value, '')
delete(key) if self[key].empty?
end if value
end
def cc; self['CC']; end
def cxx; self['CXX']; end
def cflags; self['CFLAGS']; end
def cxxflags; self['CXXFLAGS']; end
def cppflags; self['CPPFLAGS']; end
def ldflags; self['LDFLAGS']; end
def fc; self['FC']; end
def fflags; self['FFLAGS']; end
def fcflags; self['FCFLAGS']; end
def compiler
@compiler ||= if (cc = ARGV.cc)
warn_about_non_apple_gcc($1) if cc =~ GNU_GCC_REGEXP
fetch_compiler(cc, "--cc")
elsif (cc = homebrew_cc)
warn_about_non_apple_gcc($1) if cc =~ GNU_GCC_REGEXP
compiler = fetch_compiler(cc, "HOMEBREW_CC")
if @formula
compilers = [compiler] + CompilerSelector.compilers
compiler = CompilerSelector.select_for(@formula, compilers)
end
compiler
elsif @formula
CompilerSelector.select_for(@formula)
else
MacOS.default_compiler
end
end
def determine_cc
COMPILER_SYMBOL_MAP.invert.fetch(compiler, compiler)
end
COMPILERS.each do |compiler|
define_method(compiler) do
@compiler = compiler
self.cc = determine_cc
self.cxx = determine_cxx
end
end
# Snow Leopard defines an NCURSES value the opposite of most distros
# See: http://bugs.python.org/issue6848
# Currently only used by aalib in core
def ncurses_define
append 'CPPFLAGS', "-DNCURSES_OPAQUE=0"
end
def userpaths!
paths = ORIGINAL_PATHS.map { |p| p.realpath.to_s rescue nil } - %w{/usr/X11/bin /opt/X11/bin}
self['PATH'] = paths.unshift(*self['PATH'].split(File::PATH_SEPARATOR)).uniq.join(File::PATH_SEPARATOR)
# XXX hot fix to prefer brewed stuff (e.g. python) over /usr/bin.
prepend_path 'PATH', HOMEBREW_PREFIX/'bin'
end
def fortran
flags = []
if fc
ohai "Building with an alternative Fortran compiler"
puts "This is unsupported."
self['F77'] ||= fc
if ARGV.include? '--default-fortran-flags'
flags = FC_FLAG_VARS.reject { |key| self[key] }
elsif values_at(*FC_FLAG_VARS).compact.empty?
opoo <<-EOS.undent
No Fortran optimization information was provided. You may want to consider
setting FCFLAGS and FFLAGS or pass the `--default-fortran-flags` option to
`brew install` if your compiler is compatible with GCC.
If you like the default optimization level of your compiler, ignore this
warning.
EOS
end
else
if (gfortran = which('gfortran', (HOMEBREW_PREFIX/'bin').to_s))
ohai "Using Homebrew-provided fortran compiler."
elsif (gfortran = which('gfortran', ORIGINAL_PATHS.join(File::PATH_SEPARATOR)))
ohai "Using a fortran compiler found at #{gfortran}."
end
if gfortran
puts "This may be changed by setting the FC environment variable."
self['FC'] = self['F77'] = gfortran
flags = FC_FLAG_VARS
end
end
flags.each { |key| self[key] = cflags }
set_cpu_flags(flags)
end
# ld64 is a newer linker provided for Xcode 2.5
def ld64
ld64 = Formulary.factory('ld64')
self['LD'] = ld64.bin/'ld'
append "LDFLAGS", "-B#{ld64.bin}/"
end
def gcc_version_formula(version)
gcc_name = "gcc-#{version}"
gcc_version_name = "gcc#{version.delete('.')}"
gcc_path = HOMEBREW_PREFIX.join "opt/gcc/bin/#{gcc_name}"
gcc_formula = Formulary.factory "gcc"
gcc_versions_path = \
HOMEBREW_PREFIX.join "opt/#{gcc_version_name}/bin/#{gcc_name}"
if gcc_path.exist?
gcc_formula
elsif gcc_versions_path.exist?
Formulary.factory gcc_version_name
elsif gcc_formula.version.to_s.include?(version)
gcc_formula
elsif (gcc_versions_formula = Formulary.factory(gcc_version_name) rescue nil)
gcc_versions_formula
else
gcc_formula
end
end
def warn_about_non_apple_gcc(gcc)
gcc_name = 'gcc' + gcc.delete('.')
begin
gcc_formula = gcc_version_formula(gcc)
if gcc_formula.name == "gcc"
return if gcc_formula.opt_prefix.exist?
raise <<-EOS.undent
The Homebrew GCC was not installed.
You must:
brew install gcc
EOS
end
if !gcc_formula.opt_prefix.exist?
raise <<-EOS.undent
The requested Homebrew GCC, #{gcc_name}, was not installed.
You must:
brew tap homebrew/versions
brew install #{gcc_name}
EOS
end
rescue FormulaUnavailableError
raise <<-EOS.undent
Homebrew GCC requested, but formula #{gcc_name} not found!
You may need to: brew tap homebrew/versions
EOS
end
end
def permit_arch_flags; end
private
def cc= val
self["CC"] = self["OBJC"] = val.to_s
end
def cxx= val
self["CXX"] = self["OBJCXX"] = val.to_s
end
def homebrew_cc
self["HOMEBREW_CC"]
end
def fetch_compiler(value, source)
COMPILER_SYMBOL_MAP.fetch(value) do |other|
case other
when GNU_GCC_REGEXP
other
else
raise "Invalid value for #{source}: #{other}"
end
end
end
end

View File

@ -0,0 +1,336 @@
# -*- coding: utf-8 -*-
require 'hardware'
require 'os/mac'
require 'extend/ENV/shared'
module Stdenv
include SharedEnvExtension
SAFE_CFLAGS_FLAGS = "-w -pipe"
DEFAULT_FLAGS = '-march=core2 -msse4'
def self.extended(base)
unless ORIGINAL_PATHS.include? HOMEBREW_PREFIX/'bin'
base.prepend_path 'PATH', "#{HOMEBREW_PREFIX}/bin"
end
end
def setup_build_environment(formula=nil)
super
if MacOS.version >= :mountain_lion
# Mountain Lion's sed is stricter, and errors out when
# it encounters files with mixed character sets
delete('LC_ALL')
self['LC_CTYPE']="C"
end
# Set the default pkg-config search path, overriding the built-in paths
# Anything in PKG_CONFIG_PATH is searched before paths in this variable
self['PKG_CONFIG_LIBDIR'] = determine_pkg_config_libdir
# make any aclocal stuff installed in Homebrew available
self['ACLOCAL_PATH'] = "#{HOMEBREW_PREFIX}/share/aclocal" if MacOS::Xcode.provides_autotools?
self['MAKEFLAGS'] = "-j#{self.make_jobs}"
unless HOMEBREW_PREFIX.to_s == '/usr/local'
# /usr/local is already an -isystem and -L directory so we skip it
self['CPPFLAGS'] = "-isystem#{HOMEBREW_PREFIX}/include"
self['LDFLAGS'] = "-L#{HOMEBREW_PREFIX}/lib"
# CMake ignores the variables above
self['CMAKE_PREFIX_PATH'] = HOMEBREW_PREFIX.to_s
end
frameworks = HOMEBREW_PREFIX.join("Frameworks")
if frameworks.directory?
append "CPPFLAGS", "-F#{frameworks}"
append "LDFLAGS", "-F#{frameworks}"
self["CMAKE_FRAMEWORK_PATH"] = frameworks.to_s
end
# Os is the default Apple uses for all its stuff so let's trust them
set_cflags "-Os #{SAFE_CFLAGS_FLAGS}"
append 'LDFLAGS', '-Wl,-headerpad_max_install_names'
send(compiler)
if cc =~ GNU_GCC_REGEXP
gcc_formula = gcc_version_formula($1)
append_path "PATH", gcc_formula.opt_bin.to_s
end
# Add lib and include etc. from the current macosxsdk to compiler flags:
macosxsdk MacOS.version
if MacOS::Xcode.without_clt?
append_path "PATH", "#{MacOS::Xcode.prefix}/usr/bin"
append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin"
end
end
def determine_pkg_config_libdir
paths = []
paths << "#{HOMEBREW_PREFIX}/lib/pkgconfig"
paths << "#{HOMEBREW_PREFIX}/share/pkgconfig"
paths << "#{HOMEBREW_LIBRARY}/ENV/pkgconfig/#{MacOS.version}"
paths << "/usr/lib/pkgconfig"
paths.select { |d| File.directory? d }.join(File::PATH_SEPARATOR)
end
def deparallelize
remove 'MAKEFLAGS', /-j\d+/
end
alias_method :j1, :deparallelize
# These methods are no-ops for compatibility.
%w{fast O4 Og}.each { |opt| define_method(opt) {} }
%w{O3 O2 O1 O0 Os}.each do |opt|
define_method opt do
remove_from_cflags(/-O./)
append_to_cflags "-#{opt}"
end
end
def determine_cc
s = super
MacOS.locate(s) || Pathname.new(s)
end
def determine_cxx
dir, base = determine_cc.split
dir / base.to_s.sub("gcc", "g++").sub("clang", "clang++")
end
def gcc_4_0
super
set_cpu_cflags '-march=nocona -mssse3'
end
alias_method :gcc_4_0_1, :gcc_4_0
def gcc
super
set_cpu_cflags
end
alias_method :gcc_4_2, :gcc
GNU_GCC_VERSIONS.each do |n|
define_method(:"gcc-4.#{n}") do
super()
set_cpu_cflags
end
end
def llvm
super
set_cpu_cflags
end
def clang
super
replace_in_cflags(/-Xarch_#{Hardware::CPU.arch_32_bit} (-march=\S*)/, '\1')
# Clang mistakenly enables AES-NI on plain Nehalem
map = Hardware::CPU.optimization_flags
map = map.merge(:nehalem => "-march=native -Xclang -target-feature -Xclang -aes")
set_cpu_cflags "-march=native", map
end
def remove_macosxsdk version=MacOS.version
# Clear all lib and include dirs from CFLAGS, CPPFLAGS, LDFLAGS that were
# previously added by macosxsdk
version = version.to_s
remove_from_cflags(/ ?-mmacosx-version-min=10\.\d/)
delete('MACOSX_DEPLOYMENT_TARGET')
delete('CPATH')
remove 'LDFLAGS', "-L#{HOMEBREW_PREFIX}/lib"
if (sdk = MacOS.sdk_path(version)) && !MacOS::CLT.installed?
delete('SDKROOT')
remove_from_cflags "-isysroot #{sdk}"
remove 'CPPFLAGS', "-isysroot #{sdk}"
remove 'LDFLAGS', "-isysroot #{sdk}"
if HOMEBREW_PREFIX.to_s == '/usr/local'
delete('CMAKE_PREFIX_PATH')
else
# It was set in setup_build_environment, so we have to restore it here.
self['CMAKE_PREFIX_PATH'] = HOMEBREW_PREFIX.to_s
end
remove 'CMAKE_FRAMEWORK_PATH', "#{sdk}/System/Library/Frameworks"
end
end
def macosxsdk version=MacOS.version
return unless OS.mac?
# Sets all needed lib and include dirs to CFLAGS, CPPFLAGS, LDFLAGS.
remove_macosxsdk
version = version.to_s
append_to_cflags("-mmacosx-version-min=#{version}")
self['MACOSX_DEPLOYMENT_TARGET'] = version
self['CPATH'] = "#{HOMEBREW_PREFIX}/include"
prepend 'LDFLAGS', "-L#{HOMEBREW_PREFIX}/lib"
if (sdk = MacOS.sdk_path(version)) && !MacOS::CLT.installed?
# Extra setup to support Xcode 4.3+ without CLT.
self['SDKROOT'] = sdk
# Tell clang/gcc where system include's are:
append_path 'CPATH', "#{sdk}/usr/include"
# The -isysroot is needed, too, because of the Frameworks
append_to_cflags "-isysroot #{sdk}"
append 'CPPFLAGS', "-isysroot #{sdk}"
# And the linker needs to find sdk/usr/lib
append 'LDFLAGS', "-isysroot #{sdk}"
# Needed to build cmake itself and perhaps some cmake projects:
append_path 'CMAKE_PREFIX_PATH', "#{sdk}/usr"
append_path 'CMAKE_FRAMEWORK_PATH', "#{sdk}/System/Library/Frameworks"
end
end
def minimal_optimization
set_cflags "-Os #{SAFE_CFLAGS_FLAGS}"
macosxsdk unless MacOS::CLT.installed?
end
def no_optimization
set_cflags SAFE_CFLAGS_FLAGS
macosxsdk unless MacOS::CLT.installed?
end
# Some configure scripts won't find libxml2 without help
def libxml2
if MacOS::CLT.installed?
append 'CPPFLAGS', '-I/usr/include/libxml2'
else
# Use the includes form the sdk
append 'CPPFLAGS', "-I#{MacOS.sdk_path}/usr/include/libxml2"
end
end
def x11
# There are some config scripts here that should go in the PATH
append_path "PATH", MacOS::X11.bin.to_s
# Append these to PKG_CONFIG_LIBDIR so they are searched
# *after* our own pkgconfig directories, as we dupe some of the
# libs in XQuartz.
append_path "PKG_CONFIG_LIBDIR", "#{MacOS::X11.lib}/pkgconfig"
append_path "PKG_CONFIG_LIBDIR", "#{MacOS::X11.share}/pkgconfig"
append "LDFLAGS", "-L#{MacOS::X11.lib}"
append_path "CMAKE_PREFIX_PATH", MacOS::X11.prefix.to_s
append_path "CMAKE_INCLUDE_PATH", MacOS::X11.include.to_s
append_path "CMAKE_INCLUDE_PATH", "#{MacOS::X11.include}/freetype2"
append "CPPFLAGS", "-I#{MacOS::X11.include}"
append "CPPFLAGS", "-I#{MacOS::X11.include}/freetype2"
append_path "ACLOCAL_PATH", "#{MacOS::X11.share}/aclocal"
if MacOS::XQuartz.provided_by_apple? and not MacOS::CLT.installed?
append_path "CMAKE_PREFIX_PATH", "#{MacOS.sdk_path}/usr/X11"
end
append "CFLAGS", "-I#{MacOS::X11.include}" unless MacOS::CLT.installed?
end
alias_method :libpng, :x11
# we've seen some packages fail to build when warnings are disabled!
def enable_warnings
remove_from_cflags '-w'
end
def m64
append_to_cflags '-m64'
append 'LDFLAGS', "-arch #{Hardware::CPU.arch_64_bit}"
end
def m32
append_to_cflags '-m32'
append 'LDFLAGS', "-arch #{Hardware::CPU.arch_32_bit}"
end
def universal_binary
append_to_cflags Hardware::CPU.universal_archs.as_arch_flags
append 'LDFLAGS', Hardware::CPU.universal_archs.as_arch_flags
if compiler != :clang && Hardware.is_32_bit?
# Can't mix "-march" for a 32-bit CPU with "-arch x86_64"
replace_in_cflags(/-march=\S*/, "-Xarch_#{Hardware::CPU.arch_32_bit} \\0")
end
end
def cxx11
if compiler == :clang
append 'CXX', '-std=c++11'
append 'CXX', '-stdlib=libc++'
elsif compiler =~ /gcc-4\.(8|9)/
append 'CXX', '-std=c++11'
else
raise "The selected compiler doesn't support C++11: #{compiler}"
end
end
def libcxx
if compiler == :clang
append 'CXX', '-stdlib=libc++'
end
end
def libstdcxx
if compiler == :clang
append 'CXX', '-stdlib=libstdc++'
end
end
def replace_in_cflags before, after
CC_FLAG_VARS.each do |key|
self[key] = self[key].sub(before, after) if has_key?(key)
end
end
# Convenience method to set all C compiler flags in one shot.
def set_cflags val
CC_FLAG_VARS.each { |key| self[key] = val }
end
# Sets architecture-specific flags for every environment variable
# given in the list `flags`.
def set_cpu_flags flags, default=DEFAULT_FLAGS, map=Hardware::CPU.optimization_flags
cflags =~ %r{(-Xarch_#{Hardware::CPU.arch_32_bit} )-march=}
xarch = $1.to_s
remove flags, %r{(-Xarch_#{Hardware::CPU.arch_32_bit} )?-march=\S*}
remove flags, %r{( -Xclang \S+)+}
remove flags, %r{-mssse3}
remove flags, %r{-msse4(\.\d)?}
append flags, xarch unless xarch.empty?
append flags, map.fetch(effective_arch, default)
end
def effective_arch
if ARGV.build_bottle?
ARGV.bottle_arch || Hardware.oldest_cpu
elsif Hardware::CPU.intel? && !Hardware::CPU.sse4?
# If the CPU doesn't support SSE4, we cannot trust -march=native or
# -march=<cpu family> to do the right thing because we might be running
# in a VM or on a Hackintosh.
Hardware.oldest_cpu
else
Hardware::CPU.family
end
end
def set_cpu_cflags default=DEFAULT_FLAGS, map=Hardware::CPU.optimization_flags
set_cpu_flags CC_FLAG_VARS, default, map
end
def make_jobs
# '-j' requires a positive integral argument
if self['HOMEBREW_MAKE_JOBS'].to_i > 0
self['HOMEBREW_MAKE_JOBS'].to_i
else
Hardware::CPU.cores
end
end
# This method does nothing in stdenv since there's no arg refurbishment
def refurbish_args; end
end

View File

@ -0,0 +1,324 @@
require 'os/mac'
require 'extend/ENV/shared'
### Why `superenv`?
# 1) Only specify the environment we need (NO LDFLAGS for cmake)
# 2) Only apply compiler specific options when we are calling that compiler
# 3) Force all incpaths and libpaths into the cc instantiation (less bugs)
# 4) Cater toolchain usage to specific Xcode versions
# 5) Remove flags that we don't want or that will break builds
# 6) Simpler code
# 7) Simpler formula that *just work*
# 8) Build-system agnostic configuration of the tool-chain
module Superenv
include SharedEnvExtension
attr_accessor :keg_only_deps, :deps, :x11
alias_method :x11?, :x11
def self.extended(base)
base.keg_only_deps = []
base.deps = []
end
def self.bin
(HOMEBREW_REPOSITORY/"Library/ENV").subdirs.reject { |d| d.basename.to_s > MacOS::Xcode.version }.max
end
def reset
super
# Configure scripts generated by autoconf 2.61 or later export as_nl, which
# we use as a heuristic for running under configure
delete("as_nl")
end
def setup_build_environment(formula=nil)
super
send(compiler)
self['MAKEFLAGS'] ||= "-j#{determine_make_jobs}"
self['PATH'] = determine_path
self['PKG_CONFIG_PATH'] = determine_pkg_config_path
self['PKG_CONFIG_LIBDIR'] = determine_pkg_config_libdir
self['HOMEBREW_CCCFG'] = determine_cccfg
self['HOMEBREW_OPTIMIZATION_LEVEL'] = 'Os'
self['HOMEBREW_BREW_FILE'] = HOMEBREW_BREW_FILE.to_s
self['HOMEBREW_PREFIX'] = HOMEBREW_PREFIX.to_s
self['HOMEBREW_TEMP'] = HOMEBREW_TEMP.to_s
self['HOMEBREW_SDKROOT'] = "#{MacOS.sdk_path}" if MacOS::Xcode.without_clt?
self['HOMEBREW_OPTFLAGS'] = determine_optflags
self['HOMEBREW_ARCHFLAGS'] = ''
self['CMAKE_PREFIX_PATH'] = determine_cmake_prefix_path
self['CMAKE_FRAMEWORK_PATH'] = determine_cmake_frameworks_path
self['CMAKE_INCLUDE_PATH'] = determine_cmake_include_path
self['CMAKE_LIBRARY_PATH'] = determine_cmake_library_path
self['ACLOCAL_PATH'] = determine_aclocal_path
self['M4'] = MacOS.locate("m4") if deps.include? "autoconf"
self["HOMEBREW_ISYSTEM_PATHS"] = determine_isystem_paths
self["HOMEBREW_INCLUDE_PATHS"] = determine_include_paths
self["HOMEBREW_LIBRARY_PATHS"] = determine_library_paths
# On 10.9 the developer tools honor the correct sysroot by default.
# On 10.7 and 10.8 we need to set it ourselves.
if MacOS::Xcode.without_clt? && (MacOS.version <= "10.8" || compiler != :clang)
self["HOMEBREW_SYSROOT"] = effective_sysroot
end
# On 10.9, the tools in /usr/bin proxy to the active developer directory.
# This means we can use them for any combination of CLT and Xcode.
self["HOMEBREW_PREFER_CLT_PROXIES"] = "1" if MacOS.version >= "10.9"
# The HOMEBREW_CCCFG ENV variable is used by the ENV/cc tool to control
# compiler flag stripping. It consists of a string of characters which act
# as flags. Some of these flags are mutually exclusive.
#
# O - Enables argument refurbishing. Only active under the
# make/bsdmake wrappers currently.
# x - Enable C++11 mode.
# g - Enable "-stdlib=libc++" for clang.
# h - Enable "-stdlib=libstdc++" for clang.
# K - Don't strip -arch <arch>, -m32, or -m64
#
# On 10.8 and newer, these flags will also be present:
# s - apply fix for sed's Unicode support
# a - apply fix for apr-1-config path
end
private
def cc= val
self["HOMEBREW_CC"] = super
end
def cxx= val
self["HOMEBREW_CXX"] = super
end
def effective_sysroot
if MacOS::Xcode.without_clt? then MacOS.sdk_path.to_s else "" end
end
def determine_cxx
determine_cc.to_s.gsub('gcc', 'g++').gsub('clang', 'clang++')
end
def determine_path
paths = [Superenv.bin]
# Formula dependencies can override standard tools.
paths += deps.map { |dep| "#{HOMEBREW_PREFIX}/opt/#{dep}/bin" }
# On 10.9, there are shims for all tools in /usr/bin.
# On 10.7 and 10.8 we need to add these directories ourselves.
if MacOS::Xcode.without_clt? && MacOS.version <= "10.8"
paths << "#{MacOS::Xcode.prefix}/usr/bin"
paths << "#{MacOS::Xcode.toolchain_path}/usr/bin"
end
paths << MacOS::X11.bin.to_s if x11?
paths += %w{/usr/bin /bin /usr/sbin /sbin}
# Homebrew's apple-gcc42 will be outside the PATH in superenv,
# so xcrun may not be able to find it
case homebrew_cc
when "gcc-4.2"
begin
apple_gcc42 = Formulary.factory('apple-gcc42')
rescue Exception # in --debug, catch bare exceptions too
end
paths << apple_gcc42.opt_bin.to_s if apple_gcc42
when GNU_GCC_REGEXP
gcc_formula = gcc_version_formula($1)
paths << gcc_formula.opt_bin.to_s
end
paths.to_path_s
end
def determine_pkg_config_path
paths = deps.map{|dep| "#{HOMEBREW_PREFIX}/opt/#{dep}/lib/pkgconfig" }
paths += deps.map{|dep| "#{HOMEBREW_PREFIX}/opt/#{dep}/share/pkgconfig" }
paths.to_path_s
end
def determine_pkg_config_libdir
paths = %W{/usr/lib/pkgconfig #{HOMEBREW_LIBRARY}/ENV/pkgconfig/#{MacOS.version}}
paths << "#{MacOS::X11.lib}/pkgconfig" << "#{MacOS::X11.share}/pkgconfig" if x11?
paths.to_path_s
end
def determine_aclocal_path
paths = keg_only_deps.map{|dep| "#{HOMEBREW_PREFIX}/opt/#{dep}/share/aclocal" }
paths << "#{HOMEBREW_PREFIX}/share/aclocal"
paths << "#{MacOS::X11.share}/aclocal" if x11?
paths.to_path_s
end
def determine_isystem_paths
paths = []
paths << "#{HOMEBREW_PREFIX}/include"
paths << "#{effective_sysroot}/usr/include/libxml2" unless deps.include? "libxml2"
paths << "#{effective_sysroot}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << MacOS::X11.include.to_s << "#{MacOS::X11.include}/freetype2" if x11?
paths << "#{effective_sysroot}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths.to_path_s
end
def determine_include_paths
keg_only_deps.map { |dep| "#{HOMEBREW_PREFIX}/opt/#{dep}/include" }.to_path_s
end
def determine_library_paths
paths = keg_only_deps.map { |dep| "#{HOMEBREW_PREFIX}/opt/#{dep}/lib" }
paths << "#{HOMEBREW_PREFIX}/lib"
paths << MacOS::X11.lib.to_s if x11?
paths << "#{effective_sysroot}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries"
paths.to_path_s
end
def determine_cmake_prefix_path
paths = keg_only_deps.map { |dep| "#{HOMEBREW_PREFIX}/opt/#{dep}" }
paths << HOMEBREW_PREFIX.to_s
paths.to_path_s
end
def determine_cmake_include_path
paths = []
paths << "#{effective_sysroot}/usr/include/libxml2" unless deps.include? "libxml2"
paths << "#{effective_sysroot}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << MacOS::X11.include.to_s << "#{MacOS::X11.include}/freetype2" if x11?
paths << "#{effective_sysroot}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths.to_path_s
end
def determine_cmake_library_path
paths = []
paths << MacOS::X11.lib.to_s if x11?
paths << "#{effective_sysroot}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries"
paths.to_path_s
end
def determine_cmake_frameworks_path
paths = deps.map { |dep| "#{HOMEBREW_PREFIX}/opt/#{dep}/Frameworks" }
paths << "#{effective_sysroot}/System/Library/Frameworks" if MacOS::Xcode.without_clt?
paths.to_path_s
end
def determine_make_jobs
if (j = self['HOMEBREW_MAKE_JOBS'].to_i) < 1
Hardware::CPU.cores
else
j
end
end
def determine_optflags
if ARGV.build_bottle?
arch = ARGV.bottle_arch || Hardware.oldest_cpu
Hardware::CPU.optimization_flags.fetch(arch)
elsif Hardware::CPU.intel? && !Hardware::CPU.sse4?
Hardware::CPU.optimization_flags.fetch(Hardware.oldest_cpu)
elsif compiler == :clang
"-march=native"
# This is mutated elsewhere, so return an empty string in this case
else
""
end
end
def determine_cccfg
s = ""
# Fix issue with sed barfing on unicode characters on Mountain Lion
s << 's' if MacOS.version >= :mountain_lion
# Fix issue with >= 10.8 apr-1-config having broken paths
s << 'a' if MacOS.version >= :mountain_lion
s
end
public
def deparallelize
delete('MAKEFLAGS')
end
alias_method :j1, :deparallelize
def make_jobs
self['MAKEFLAGS'] =~ /-\w*j(\d)+/
[$1.to_i, 1].max
end
def universal_binary
self['HOMEBREW_ARCHFLAGS'] = Hardware::CPU.universal_archs.as_arch_flags
# GCC doesn't accept "-march" for a 32-bit CPU with "-arch x86_64"
if compiler != :clang && Hardware.is_32_bit?
self['HOMEBREW_OPTFLAGS'] = self['HOMEBREW_OPTFLAGS'].sub(
/-march=\S*/,
"-Xarch_#{Hardware::CPU.arch_32_bit} \\0"
)
end
end
def permit_arch_flags
append "HOMEBREW_CCCFG", "K"
end
def m32
append "HOMEBREW_ARCHFLAGS", "-m32"
end
def m64
append "HOMEBREW_ARCHFLAGS", "-m64"
end
def cxx11
case homebrew_cc
when "clang"
append 'HOMEBREW_CCCFG', "x", ''
append 'HOMEBREW_CCCFG', "g", ''
when /gcc-4\.(8|9)/
append 'HOMEBREW_CCCFG', "x", ''
else
raise "The selected compiler doesn't support C++11: #{homebrew_cc}"
end
end
def libcxx
append "HOMEBREW_CCCFG", "g", "" if compiler == :clang
end
def libstdcxx
append "HOMEBREW_CCCFG", "h", "" if compiler == :clang
end
def refurbish_args
append 'HOMEBREW_CCCFG', "O", ''
end
%w{O3 O2 O1 O0 Os}.each do |opt|
define_method opt do
self['HOMEBREW_OPTIMIZATION_LEVEL'] = opt
end
end
def noop(*args); end
noops = []
# These methods are no longer necessary under superenv, but are needed to
# maintain an interface compatible with stdenv.
noops.concat %w{fast O4 Og libxml2 set_cpu_flags macosxsdk remove_macosxsdk}
# These methods provide functionality that has not yet been ported to
# superenv.
noops.concat %w{gcc_4_0_1 minimal_optimization no_optimization enable_warnings}
noops.each { |m| alias_method m, :noop }
end
class Array
def to_path_s
map(&:to_s).uniq.select{|s| File.directory? s }.join(File::PATH_SEPARATOR).chuzzle
end
end

View File

@ -0,0 +1,7 @@
module Enumerable
def group_by
inject({}) do |h, e|
h.fetch(yield(e)) { |k| h[k] = [] } << e; h
end
end unless method_defined?(:group_by)
end

View File

@ -0,0 +1,110 @@
require 'fileutils'
# We enhance FileUtils to make our Formula code more readable.
module FileUtils
# Create a temporary directory then yield. When the block returns,
# recursively delete the temporary directory.
def mktemp(prefix=name)
# I used /tmp rather than `mktemp -td` because that generates a directory
# name with exotic characters like + in it, and these break badly written
# scripts that don't escape strings before trying to regexp them :(
# If the user has FileVault enabled, then we can't mv symlinks from the
# /tmp volume to the other volume. So we let the user override the tmp
# prefix if they need to.
tempd = with_system_path { `mktemp -d #{HOMEBREW_TEMP}/#{prefix}-XXXXXX` }.chuzzle
raise "Failed to create sandbox" if tempd.nil?
prevd = pwd
cd tempd
yield
ensure
cd prevd if prevd
ignore_interrupts{ rm_r tempd } if tempd
end
module_function :mktemp
# A version of mkdir that also changes to that folder in a block.
alias_method :old_mkdir, :mkdir
def mkdir name, &block
old_mkdir(name)
if block_given?
chdir name do
yield
end
end
end
module_function :mkdir
# The #copy_metadata method in all current versions of Ruby has a
# bad bug which causes copying symlinks across filesystems to fail;
# see #14710.
# This was resolved in Ruby HEAD after the release of 1.9.3p194, but
# never backported into the 1.9.3 branch. Fixed in 2.0.0.
# The monkey-patched method here is copied directly from upstream fix.
if RUBY_VERSION < "2.0.0"
class Entry_
alias_method :old_copy_metadata, :copy_metadata
def copy_metadata(path)
st = lstat()
if !st.symlink?
File.utime st.atime, st.mtime, path
end
begin
if st.symlink?
begin
File.lchown st.uid, st.gid, path
rescue NotImplementedError
end
else
File.chown st.uid, st.gid, path
end
rescue Errno::EPERM
# clear setuid/setgid
if st.symlink?
begin
File.lchmod st.mode & 01777, path
rescue NotImplementedError
end
else
File.chmod st.mode & 01777, path
end
else
if st.symlink?
begin
File.lchmod st.mode, path
rescue NotImplementedError
end
else
File.chmod st.mode, path
end
end
end
end
end
private
# Run scons using a Homebrew-installed version, instead of whatever
# is in the user's PATH
def scons *args
system Formulary.factory("scons").opt_bin/"scons", *args
end
def rake *args
system RUBY_BIN/'rake', *args
end
alias_method :old_ruby, :ruby if method_defined?(:ruby)
def ruby *args
system RUBY_PATH, *args
end
def xcodebuild *args
removed = ENV.remove_cc_etc
system "xcodebuild", *args
ensure
ENV.update(removed)
end
end

View File

@ -0,0 +1,14 @@
class Module
def attr_rw(*attrs)
file, line, _ = caller.first.split(":")
line = line.to_i
attrs.each do |attr|
module_eval <<-EOS, file, line
def #{attr}(val=nil)
val.nil? ? @#{attr} : @#{attr} = val
end
EOS
end
end
end

View File

@ -0,0 +1,496 @@
require 'pathname'
require 'mach'
require 'resource'
require 'metafiles'
# we enhance pathname to make our code more readable
class Pathname
include MachO
BOTTLE_EXTNAME_RX = /(\.[a-z_]+(32)?\.bottle\.(\d+\.)?tar\.gz)$/
def install *sources
sources.each do |src|
case src
when Resource
src.stage(self)
when Resource::Partial
src.resource.stage { install(*src.files) }
when Array
if src.empty?
opoo "tried to install empty array to #{self}"
return
end
src.each {|s| install_p(s) }
when Hash
if src.empty?
opoo "tried to install empty hash to #{self}"
return
end
src.each {|s, new_basename| install_p(s, new_basename) }
else
install_p(src)
end
end
end
def install_p src, new_basename = nil
raise Errno::ENOENT, src.to_s unless File.symlink?(src) || File.exist?(src)
if new_basename
new_basename = File.basename(new_basename) # rationale: see Pathname.+
dst = self+new_basename
else
dst = self
end
src = src.to_s
dst = dst.to_s
dst = yield(src, dst) if block_given?
return unless dst
mkpath
# Use FileUtils.mv over File.rename to handle filesystem boundaries. If src
# is a symlink, and its target is moved first, FileUtils.mv will fail:
# https://bugs.ruby-lang.org/issues/7707
# In that case, use the system "mv" command.
if File.symlink? src
raise unless Kernel.system 'mv', src, dst
else
FileUtils.mv src, dst
end
end
protected :install_p
# Creates symlinks to sources in this folder.
def install_symlink *sources
sources.each do |src|
case src
when Array
src.each {|s| install_symlink_p(s) }
when Hash
src.each {|s, new_basename| install_symlink_p(s, new_basename) }
else
install_symlink_p(src)
end
end
end
def install_symlink_p src, new_basename=src
src = Pathname(src).expand_path(self)
dst = join File.basename(new_basename)
mkpath
FileUtils.ln_sf src.relative_path_from(dst.parent), dst
end
protected :install_symlink_p
# we assume this pathname object is a file obviously
alias_method :old_write, :write if method_defined?(:write)
def write(content, *open_args)
raise "Will not overwrite #{to_s}" if exist?
dirname.mkpath
open("w", *open_args) { |f| f.write(content) }
end
def binwrite(contents, *open_args)
open("wb", *open_args) { |f| f.write(contents) }
end unless method_defined?(:binwrite)
def binread(*open_args)
open("rb", *open_args) { |f| f.read }
end unless method_defined?(:binread)
# NOTE always overwrites
def atomic_write content
require "tempfile"
tf = Tempfile.new(basename.to_s, dirname)
tf.binmode
tf.write(content)
begin
old_stat = stat
rescue Errno::ENOENT
old_stat = default_stat
end
uid = Process.uid
gid = Process.groups.delete(old_stat.gid) { Process.gid }
begin
tf.chown(uid, gid)
tf.chmod(old_stat.mode)
rescue Errno::EPERM
end
File.rename(tf.path, self)
ensure
tf.close!
end
def default_stat
sentinel = parent.join(".brew.#{Process.pid}.#{rand(Time.now.to_i)}")
sentinel.open("w") { }
sentinel.stat
ensure
sentinel.unlink
end
private :default_stat
def cp dst
opoo "Pathname#cp is deprecated, use FileUtils.cp"
if file?
FileUtils.cp to_s, dst
else
FileUtils.cp_r to_s, dst
end
return dst
end
def cp_path_sub pattern, replacement
raise "#{self} does not exist" unless self.exist?
src = self.to_s
dst = src.sub(pattern, replacement)
raise "#{src} is the same file as #{dst}" if src == dst
dst_path = Pathname.new dst
if self.directory?
dst_path.mkpath
return
end
dst_path.dirname.mkpath
dst = yield(src, dst) if block_given?
FileUtils.cp(src, dst)
end
# extended to support common double extensions
alias extname_old extname
def extname(path=to_s)
BOTTLE_EXTNAME_RX.match(path)
return $1 if $1
/(\.(tar|cpio|pax)\.(gz|bz2|lz|xz|Z))$/.match(path)
return $1 if $1
return File.extname(path)
end
# for filetypes we support, basename without extension
def stem
File.basename((path = to_s), extname(path))
end
# I don't trust the children.length == 0 check particularly, not to mention
# it is slow to enumerate the whole directory just to see if it is empty,
# instead rely on good ol' libc and the filesystem
def rmdir_if_possible
rmdir
true
rescue Errno::ENOTEMPTY
if (ds_store = self+'.DS_Store').exist? && children.length == 1
ds_store.unlink
retry
else
false
end
rescue Errno::EACCES, Errno::ENOENT
false
end
def chmod_R perms
opoo "Pathname#chmod_R is deprecated, use FileUtils.chmod_R"
require 'fileutils'
FileUtils.chmod_R perms, to_s
end
def version
require 'version'
Version.parse(self)
end
def compression_type
case extname
when ".jar", ".war"
# Don't treat jars or wars as compressed
return
when ".gz"
# If the filename ends with .gz not preceded by .tar
# then we want to gunzip but not tar
return :gzip_only
when ".bz2"
return :bzip2_only
end
# Get enough of the file to detect common file types
# POSIX tar magic has a 257 byte offset
# magic numbers stolen from /usr/share/file/magic/
case open('rb') { |f| f.read(262) }
when /^PK\003\004/n then :zip
when /^\037\213/n then :gzip
when /^BZh/n then :bzip2
when /^\037\235/n then :compress
when /^.{257}ustar/n then :tar
when /^\xFD7zXZ\x00/n then :xz
when /^LZIP/n then :lzip
when /^Rar!/n then :rar
when /^7z\xBC\xAF\x27\x1C/n then :p7zip
when /^xar!/n then :xar
else
# This code so that bad-tarballs and zips produce good error messages
# when they don't unarchive properly.
case extname
when ".tar.gz", ".tgz", ".tar.bz2", ".tbz" then :tar
when ".zip" then :zip
end
end
end
def text_executable?
%r[^#!\s*\S+] === open('r') { |f| f.read(1024) }
end
def incremental_hash(klass)
digest = klass.new
if digest.respond_to?(:file)
digest.file(self)
else
buf = ""
open("rb") { |f| digest << buf while f.read(1024, buf) }
end
digest.hexdigest
end
def sha1
require 'digest/sha1'
incremental_hash(Digest::SHA1)
end
def sha256
require 'digest/sha2'
incremental_hash(Digest::SHA2)
end
def verify_checksum expected
raise ChecksumMissingError if expected.nil? or expected.empty?
actual = Checksum.new(expected.hash_type, send(expected.hash_type).downcase)
raise ChecksumMismatchError.new(self, expected, actual) unless expected == actual
end
# FIXME eliminate the places where we rely on this method
alias_method :to_str, :to_s unless method_defined?(:to_str)
def cd
Dir.chdir(self){ yield }
end
def subdirs
children.select{ |child| child.directory? }
end
def resolved_path
self.symlink? ? dirname+readlink : self
end
def resolved_path_exists?
link = readlink
rescue ArgumentError
# The link target contains NUL bytes
false
else
(dirname+link).exist?
end
def make_relative_symlink(src)
dirname.mkpath
File.symlink(src.relative_path_from(dirname), self)
end
def /(other)
unless other.respond_to?(:to_str) || other.respond_to?(:to_path)
opoo "Pathname#/ called on #{inspect} with #{other.inspect} as an argument"
puts "This behavior is deprecated, please pass either a String or a Pathname"
end
self + other.to_s
end unless method_defined?(:/)
def ensure_writable
saved_perms = nil
unless writable_real?
saved_perms = stat.mode
chmod 0644
end
yield
ensure
chmod saved_perms if saved_perms
end
def install_info
quiet_system "/usr/bin/install-info", "--quiet", to_s, "#{dirname}/dir"
end
def uninstall_info
quiet_system "/usr/bin/install-info", "--delete", "--quiet", to_s, "#{dirname}/dir"
end
def find_formula
[join("Formula"), join("HomebrewFormula"), self].each do |d|
if d.exist?
d.children.each do |pn|
yield pn if pn.extname == ".rb"
end
break
end
end
end
# Writes an exec script in this folder for each target pathname
def write_exec_script *targets
targets.flatten!
if targets.empty?
opoo "tried to write exec scripts to #{self} for an empty list of targets"
return
end
mkpath
targets.each do |target|
target = Pathname.new(target) # allow pathnames or strings
(self+target.basename()).write <<-EOS.undent
#!/bin/bash
exec "#{target}" "$@"
EOS
end
end
# Writes an exec script that sets environment variables
def write_env_script target, env
env_export = ''
env.each {|key, value| env_export += "#{key}=\"#{value}\" "}
dirname.mkpath
self.write <<-EOS.undent
#!/bin/bash
#{env_export}exec "#{target}" "$@"
EOS
end
# Writes a wrapper env script and moves all files to the dst
def env_script_all_files dst, env
dst.mkpath
Pathname.glob("#{self}/*") do |file|
dst.install_p file
new_file = dst+file.basename
file.write_env_script(new_file, env)
end
end
# Writes an exec script that invokes a java jar
def write_jar_script target_jar, script_name, java_opts=""
mkpath
(self+script_name).write <<-EOS.undent
#!/bin/bash
exec java #{java_opts} -jar #{target_jar} "$@"
EOS
end
def install_metafiles from=Pathname.pwd
Pathname(from).children.each do |p|
next if p.directory?
next unless Metafiles.copy?(p.basename.to_s)
# Some software symlinks these files (see help2man.rb)
filename = p.resolved_path
# Some software links metafiles together, so by the time we iterate to one of them
# we may have already moved it. libxml2's COPYING and Copyright are affected by this.
next unless filename.exist?
filename.chmod 0644
install(filename)
end
end
def abv
out=''
n=`find #{to_s} -type f ! -name .DS_Store | wc -l`.to_i
out << "#{n} files, " if n > 1
out << `/usr/bin/du -hs #{to_s} | cut -d"\t" -f1`.strip
end
# We redefine these private methods in order to add the /o modifier to
# the Regexp literals, which forces string interpolation to happen only
# once instead of each time the method is called. This is fixed in 1.9+.
if RUBY_VERSION <= "1.8.7"
alias_method :old_chop_basename, :chop_basename
def chop_basename(path)
base = File.basename(path)
if /\A#{Pathname::SEPARATOR_PAT}?\z/o =~ base
return nil
else
return path[0, path.rindex(base)], base
end
end
private :chop_basename
alias_method :old_prepend_prefix, :prepend_prefix
def prepend_prefix(prefix, relpath)
if relpath.empty?
File.dirname(prefix)
elsif /#{SEPARATOR_PAT}/o =~ prefix
prefix = File.dirname(prefix)
prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
prefix + relpath
else
prefix + relpath
end
end
private :prepend_prefix
elsif RUBY_VERSION == "2.0.0"
# https://bugs.ruby-lang.org/issues/9915
prepend Module.new {
def inspect
super.force_encoding(@path.encoding)
end
}
end
end
module ObserverPathnameExtension
class << self
attr_accessor :n, :d
def reset_counts!
@n = @d = 0
end
def total
n + d
end
def counts
[n, d]
end
end
def unlink
super
puts "rm #{to_s}" if ARGV.verbose?
ObserverPathnameExtension.n += 1
end
def rmdir
super
puts "rmdir #{to_s}" if ARGV.verbose?
ObserverPathnameExtension.d += 1
end
def make_relative_symlink src
super
puts "ln -s #{src.relative_path_from(dirname)} #{basename}" if ARGV.verbose?
ObserverPathnameExtension.n += 1
end
def install_info
super
puts "info #{to_s}" if ARGV.verbose?
end
def uninstall_info
super
puts "uninfo #{to_s}" if ARGV.verbose?
end
end

View File

@ -0,0 +1,104 @@
class String
def undent
gsub(/^.{#{(slice(/^ +/) || '').length}}/, '')
end
# eg:
# if foo then <<-EOS.undent_________________________________________________________72
# Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
# eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
# minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
# ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
# voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
# sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
# mollit anim id est laborum.
# EOS
alias_method :undent_________________________________________________________72, :undent
def start_with?(*prefixes)
prefixes.any? do |prefix|
if prefix.respond_to?(:to_str)
prefix = prefix.to_str
self[0, prefix.length] == prefix
end
end
end unless method_defined?(:start_with?)
def end_with?(*suffixes)
suffixes.any? do |suffix|
if suffix.respond_to?(:to_str)
suffix = suffix.to_str
self[-suffix.length, suffix.length] == suffix
end
end
end unless method_defined?(:end_with?)
# 1.8.7 or later; used in bottle code
def rpartition(separator)
if ind = rindex(separator)
[slice(0, ind), separator, slice(ind+1, -1) || '']
else
['', '', dup]
end
end unless method_defined?(:rpartition)
# String.chomp, but if result is empty: returns nil instead.
# Allows `chuzzle || foo` short-circuits.
def chuzzle
s = chomp
s unless s.empty?
end
end
class NilClass
def chuzzle; end
end
# used by the inreplace function (in utils.rb)
module StringInreplaceExtension
attr_accessor :errors
def self.extended(str)
str.errors = []
end
def sub! before, after
result = super
unless result
errors << "expected replacement of #{before.inspect} with #{after.inspect}"
end
result
end
# Warn if nothing was replaced
def gsub! before, after, audit_result=true
result = super(before, after)
if audit_result && result.nil?
errors << "expected replacement of #{before.inspect} with #{after.inspect}"
end
result
end
# Looks for Makefile style variable defintions and replaces the
# value with "new_value", or removes the definition entirely.
def change_make_var! flag, new_value
unless gsub!(/^#{Regexp.escape(flag)}[ \t]*=[ \t]*(.*)$/, "#{flag}=#{new_value}", false)
errors << "expected to change #{flag.inspect} to #{new_value.inspect}"
end
end
# Removes variable assignments completely.
def remove_make_var! flags
Array(flags).each do |flag|
# Also remove trailing \n, if present.
unless gsub!(/^#{Regexp.escape(flag)}[ \t]*=.*$\n?/, "", false)
errors << "expected to remove #{flag.inspect}"
end
end
end
# Finds the specified variable
def get_make_var flag
self[/^#{Regexp.escape(flag)}[ \t]*=[ \t]*(.*)$/, 1]
end
end

View File

@ -0,0 +1,5 @@
class Symbol
def to_proc
proc { |*args| args.shift.send(self, *args) }
end unless method_defined?(:to_proc)
end

View File

@ -0,0 +1,111 @@
require 'extend/module'
require 'extend/fileutils'
require 'extend/pathname'
require 'extend/ARGV'
require 'extend/string'
require 'extend/symbol'
require 'extend/enumerable'
require 'os'
require 'utils'
require 'exceptions'
require 'set'
require 'rbconfig'
ARGV.extend(HomebrewArgvExtension)
HOMEBREW_VERSION = '0.9.5'
HOMEBREW_WWW = 'http://brew.sh'
def cache
if ENV['HOMEBREW_CACHE']
Pathname.new(ENV['HOMEBREW_CACHE'])
else
# we do this for historic reasons, however the cache *should* be the same
# directory whichever user is used and whatever instance of brew is executed
home_cache = Pathname.new("~/Library/Caches/Homebrew").expand_path
if home_cache.directory? and home_cache.writable_real?
home_cache
else
Pathname.new("/Library/Caches/Homebrew").extend Module.new {
def mkpath
unless exist?
super
chmod 0775
end
end
}
end
end
end
HOMEBREW_CACHE = cache
undef cache
# Where brews installed via URL are cached
HOMEBREW_CACHE_FORMULA = HOMEBREW_CACHE+"Formula"
if not defined? HOMEBREW_BREW_FILE
HOMEBREW_BREW_FILE = ENV['HOMEBREW_BREW_FILE'] || which('brew').to_s
end
HOMEBREW_PREFIX = Pathname.new(HOMEBREW_BREW_FILE).dirname.parent # Where we link under
HOMEBREW_REPOSITORY = Pathname.new(HOMEBREW_BREW_FILE).realpath.dirname.parent # Where .git is found
HOMEBREW_LIBRARY = HOMEBREW_REPOSITORY/"Library"
HOMEBREW_CONTRIB = HOMEBREW_REPOSITORY/"Library/Contributions"
# Where we store built products; /usr/local/Cellar if it exists,
# otherwise a Cellar relative to the Repository.
HOMEBREW_CELLAR = if (HOMEBREW_PREFIX+"Cellar").exist?
HOMEBREW_PREFIX+"Cellar"
else
HOMEBREW_REPOSITORY+"Cellar"
end
HOMEBREW_LOGS = Pathname.new(ENV['HOMEBREW_LOGS'] || '~/Library/Logs/Homebrew/').expand_path
HOMEBREW_TEMP = Pathname.new(ENV.fetch('HOMEBREW_TEMP', '/tmp'))
if RbConfig.respond_to?(:ruby)
RUBY_PATH = Pathname.new(RbConfig.ruby)
else
RUBY_PATH = Pathname.new(RbConfig::CONFIG["bindir"]).join(
RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]
)
end
RUBY_BIN = RUBY_PATH.dirname
if RUBY_PLATFORM =~ /darwin/
MACOS_FULL_VERSION = `/usr/bin/sw_vers -productVersion`.chomp
MACOS_VERSION = MACOS_FULL_VERSION[/10\.\d+/]
OS_VERSION = "Mac OS X #{MACOS_FULL_VERSION}"
else
MACOS_FULL_VERSION = MACOS_VERSION = "0"
OS_VERSION = RUBY_PLATFORM
end
HOMEBREW_GITHUB_API_TOKEN = ENV["HOMEBREW_GITHUB_API_TOKEN"]
HOMEBREW_USER_AGENT = "Homebrew #{HOMEBREW_VERSION} (Ruby #{RUBY_VERSION}-#{RUBY_PATCHLEVEL}; #{OS_VERSION})"
HOMEBREW_CURL_ARGS = '-f#LA'
require 'tap_constants'
module Homebrew
include FileUtils
extend self
attr_accessor :failed
alias_method :failed?, :failed
end
HOMEBREW_PULL_OR_COMMIT_URL_REGEX = %r[https://github\.com/([\w-]+)/homebrew(-[\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})]
require 'compat' unless ARGV.include? "--no-compat" or ENV['HOMEBREW_NO_COMPAT']
ORIGINAL_PATHS = ENV['PATH'].split(File::PATH_SEPARATOR).map{ |p| Pathname.new(p).expand_path rescue nil }.compact.freeze
SUDO_BAD_ERRMSG = <<-EOS.undent
You can use brew with sudo, but only if the brew executable is owned by root.
However, this is both not recommended and completely unsupported so do so at
your own risk.
EOS

View File

@ -0,0 +1,74 @@
require 'os'
class Hardware
module CPU extend self
INTEL_32BIT_ARCHS = [:i386].freeze
INTEL_64BIT_ARCHS = [:x86_64].freeze
PPC_32BIT_ARCHS = [:ppc, :ppc7400, :ppc7450, :ppc970].freeze
PPC_64BIT_ARCHS = [:ppc64].freeze
def type
@type || :dunno
end
def family
@family || :dunno
end
def cores
@cores || 1
end
def bits
@bits || 64
end
def is_32_bit?
bits == 32
end
def is_64_bit?
bits == 64
end
def intel?
type == :intel
end
def ppc?
type == :ppc
end
end
if OS.mac?
require 'os/mac/hardware'
CPU.extend MacCPUs
elsif OS.linux?
require 'os/linux/hardware'
CPU.extend LinuxCPUs
else
raise "The system `#{`uname`.chomp}' is not supported."
end
def self.cores_as_words
case Hardware::CPU.cores
when 1 then 'single'
when 2 then 'dual'
when 4 then 'quad'
else
Hardware::CPU.cores
end
end
def self.oldest_cpu
if Hardware::CPU.intel?
if Hardware::CPU.is_64_bit?
:core2
else
:core
end
else
Hardware::CPU.family
end
end
end

View File

@ -0,0 +1,31 @@
# Boxen (and perhaps others) want to override our bottling infrastructure so
# they can avoid declaring checksums in formulae files.
# Instead of periodically breaking their monkeypatches let's add some hooks that
# we can query to allow their own behaviour.
# PLEASE DO NOT EVER RENAME THIS CLASS OR ADD/REMOVE METHOD ARGUMENTS!
module Homebrew
module Hooks
module Bottles
def self.setup_formula_has_bottle &block
@has_bottle = block
true
end
def self.setup_pour_formula_bottle &block
@pour_bottle = block
true
end
def self.formula_has_bottle?(formula)
return false unless @has_bottle
@has_bottle.call formula
end
def self.pour_formula_bottle(formula)
return false unless @pour_bottle
@pour_bottle.call formula
end
end
end
end

View File

@ -0,0 +1,37 @@
module InstallRenamed
def install_p src, new_basename = nil
super do |src, dst|
dst += "/#{File.basename(src)}" if File.directory? dst
if File.directory? src
Pathname.new(dst).install Dir["#{src}/*"]
next
end
append_default_if_different(src, dst)
end
end
def cp_path_sub pattern, replacement
super do |src, dst|
append_default_if_different(src, dst)
end
end
def + path
super(path).extend(InstallRenamed)
end
def / path
super(path).extend(InstallRenamed)
end
private
def append_default_if_different src, dst
if File.file? dst and !FileUtils.identical?(src, dst)
dst += ".default"
end
dst
end
end

View File

@ -0,0 +1,430 @@
require "extend/pathname"
require "keg_fix_install_names"
require "formula_lock"
require "ostruct"
class Keg
class AlreadyLinkedError < RuntimeError
def initialize(keg)
super <<-EOS.undent
Cannot link #{keg.name}
Another version is already linked: #{keg.linked_keg_record.resolved_path}
EOS
end
end
class LinkError < RuntimeError
attr_reader :keg, :src, :dst
def initialize(keg, src, dst, cause)
@src = src
@dst = dst
@keg = keg
@cause = cause
super(cause.message)
set_backtrace(cause.backtrace)
end
end
class ConflictError < LinkError
def suggestion
conflict = Keg.for(dst)
rescue NotAKegError, Errno::ENOENT
"already exists. You may want to remove it:\n rm #{dst}\n"
else
<<-EOS.undent
is a symlink belonging to #{conflict.name}. You can unlink it:
brew unlink #{conflict.name}
EOS
end
def to_s
s = []
s << "Could not symlink #{src}"
s << "Target #{dst}" << suggestion
s << <<-EOS.undent
To force the link and overwrite all conflicting files:
brew link --overwrite #{keg.name}
To list all files that would be deleted:
brew link --overwrite --dry-run #{keg.name}
EOS
s.join("\n")
end
end
class DirectoryNotWritableError < LinkError
def to_s; <<-EOS.undent
Could not symlink #{src}
#{dst.dirname} is not writable.
EOS
end
end
# locale-specific directories have the form language[_territory][.codeset][@modifier]
LOCALEDIR_RX = /(locale|man)\/([a-z]{2}|C|POSIX)(_[A-Z]{2})?(\.[a-zA-Z\-0-9]+(@.+)?)?/
INFOFILE_RX = %r[info/([^.].*?\.info|dir)$]
TOP_LEVEL_DIRECTORIES = %w[bin etc include lib sbin share var Frameworks]
PRUNEABLE_DIRECTORIES = %w[bin etc include lib sbin share Frameworks LinkedKegs].map do |d|
case d when 'LinkedKegs' then HOMEBREW_LIBRARY/d else HOMEBREW_PREFIX/d end
end
# These paths relative to the keg's share directory should always be real
# directories in the prefix, never symlinks.
SHARE_PATHS = %w[
aclocal doc info locale man
man/man1 man/man2 man/man3 man/man4
man/man5 man/man6 man/man7 man/man8
man/cat1 man/cat2 man/cat3 man/cat4
man/cat5 man/cat6 man/cat7 man/cat8
applications gnome gnome/help icons
mime-info pixmaps sounds
]
# if path is a file in a keg then this will return the containing Keg object
def self.for path
path = path.realpath
while not path.root?
return Keg.new(path) if path.parent.parent == HOMEBREW_CELLAR.realpath
path = path.parent.realpath # realpath() prevents root? failing
end
raise NotAKegError, "#{path} is not inside a keg"
end
attr_reader :path, :name, :linked_keg_record, :opt_record
protected :path
def initialize path
raise "#{path} is not a valid keg" unless path.parent.parent.realpath == HOMEBREW_CELLAR.realpath
raise "#{path} is not a directory" unless path.directory?
@path = path
@name = path.parent.basename.to_s
@linked_keg_record = HOMEBREW_LIBRARY.join("LinkedKegs", name)
@opt_record = HOMEBREW_PREFIX.join("opt", name)
end
def fname
opoo "Keg#fname is a deprecated alias for Keg#name and will be removed soon"
name
end
def to_s
path.to_s
end
if Pathname.method_defined?(:to_path)
alias_method :to_path, :to_s
else
alias_method :to_str, :to_s
end
def inspect
"#<#{self.class.name}:#{path}>"
end
def ==(other)
instance_of?(other.class) && path == other.path
end
alias_method :eql?, :==
def hash
path.hash
end
def abv
path.abv
end
def directory?
path.directory?
end
def exist?
path.exist?
end
def /(other)
path / other
end
def join(*args)
path.join(*args)
end
def rename(*args)
path.rename(*args)
end
def linked?
linked_keg_record.symlink? &&
linked_keg_record.directory? &&
path == linked_keg_record.resolved_path
end
def remove_linked_keg_record
linked_keg_record.unlink
linked_keg_record.parent.rmdir_if_possible
end
def optlinked?
opt_record.symlink? && path == opt_record.resolved_path
end
def remove_opt_record
opt_record.unlink
opt_record.parent.rmdir_if_possible
end
def uninstall
path.rmtree
path.parent.rmdir_if_possible
remove_opt_record if optlinked?
end
def unlink
ObserverPathnameExtension.reset_counts!
dirs = []
TOP_LEVEL_DIRECTORIES.map{ |d| path.join(d) }.each do |dir|
next unless dir.exist?
dir.find do |src|
dst = HOMEBREW_PREFIX + src.relative_path_from(path)
dst.extend(ObserverPathnameExtension)
dirs << dst if dst.directory? && !dst.symlink?
# check whether the file to be unlinked is from the current keg first
if dst.symlink? && src == dst.resolved_path
dst.uninstall_info if dst.to_s =~ INFOFILE_RX
dst.unlink
Find.prune if src.directory?
end
end
end
remove_linked_keg_record if linked?
dirs.reverse_each(&:rmdir_if_possible)
ObserverPathnameExtension.total
end
def lock
FormulaLock.new(name).with_lock { yield }
end
def completion_installed? shell
dir = case shell
when :bash then path.join("etc", "bash_completion.d")
when :zsh then path.join("share", "zsh", "site-functions")
end
dir && dir.directory? && dir.children.any?
end
def plist_installed?
Dir["#{path}/*.plist"].any?
end
def python_site_packages_installed?
path.join("lib", "python2.7", "site-packages").directory?
end
def app_installed?
Dir["#{path}/{,libexec/}*.app"].any?
end
def version
require 'pkg_version'
PkgVersion.parse(path.basename.to_s)
end
def find(*args, &block)
path.find(*args, &block)
end
def link mode=OpenStruct.new
raise AlreadyLinkedError.new(self) if linked_keg_record.directory?
ObserverPathnameExtension.reset_counts!
# yeah indeed, you have to force anything you need in the main tree into
# these dirs REMEMBER that *NOT* everything needs to be in the main tree
link_dir('etc', mode) {:mkpath}
link_dir('bin', mode) {:skip_dir}
link_dir('sbin', mode) {:skip_dir}
link_dir('include', mode) {:link}
link_dir('share', mode) do |path|
case path.to_s
when 'locale/locale.alias' then :skip_file
when INFOFILE_RX then :info
when LOCALEDIR_RX then :mkpath
when *SHARE_PATHS then :mkpath
when /^icons\/.*\/icon-theme\.cache$/ then :skip_file
# all icons subfolders should also mkpath
when /^icons\// then :mkpath
when /^zsh/ then :mkpath
else :link
end
end
link_dir('lib', mode) do |path|
case path.to_s
when 'charset.alias' then :skip_file
# pkg-config database gets explicitly created
when 'pkgconfig' then :mkpath
# lib/language folders also get explicitly created
when 'dtrace' then :mkpath
when /^gdk-pixbuf/ then :mkpath
when 'ghc' then :mkpath
when 'lua' then :mkpath
when /^node/ then :mkpath
when /^ocaml/ then :mkpath
when /^perl5/ then :mkpath
when 'php' then :mkpath
when /^python[23]\.\d/ then :mkpath
when 'ruby' then :mkpath
# Everything else is symlinked to the cellar
else :link
end
end
link_dir('Frameworks', mode) do |path|
# Frameworks contain symlinks pointing into a subdir, so we have to use
# the :link strategy. However, for Foo.framework and
# Foo.framework/Versions we have to use :mkpath so that multiple formulae
# can link their versions into it and `brew [un]link` works.
if path.to_s =~ /[^\/]*\.framework(\/Versions)?$/
:mkpath
else
:link
end
end
unless mode.dry_run
make_relative_symlink(linked_keg_record, path, mode)
optlink(mode)
end
rescue LinkError
unlink
raise
else
ObserverPathnameExtension.total
end
def optlink(mode=OpenStruct.new)
opt_record.delete if opt_record.symlink? || opt_record.exist?
make_relative_symlink(opt_record, path, mode)
end
def delete_pyc_files!
find { |pn| pn.delete if pn.extname == ".pyc" }
end
private
def resolve_any_conflicts dst, mode
src = dst.resolved_path
# src itself may be a symlink, so check lstat to ensure we are dealing with
# a directory, and not a symlink pointing at a directory (which needs to be
# treated as a file). In other words, we only want to resolve one symlink.
# If it isn't a directory, make_relative_symlink will raise an exception.
if dst.symlink? && src.lstat.directory?
keg = Keg.for(src)
dst.unlink unless mode.dry_run
keg.link_dir(src, mode) { :mkpath }
return true
end
rescue NotAKegError
puts "Won't resolve conflicts for symlink #{dst} as it doesn't resolve into the Cellar" if ARGV.verbose?
end
def make_relative_symlink dst, src, mode
if dst.symlink? && src == dst.resolved_path
puts "Skipping; link already exists: #{dst}" if ARGV.verbose?
return
end
# cf. git-clean -n: list files to delete, don't really link or delete
if mode.dry_run and mode.overwrite
if dst.symlink?
puts "#{dst} -> #{dst.resolved_path}"
elsif dst.exist?
puts dst
end
return
end
# list all link targets
if mode.dry_run
puts dst
return
end
dst.delete if mode.overwrite && (dst.exist? || dst.symlink?)
dst.make_relative_symlink(src)
rescue Errno::EEXIST => e
if dst.exist?
raise ConflictError.new(self, src.relative_path_from(path), dst, e)
elsif dst.symlink?
dst.unlink
retry
end
rescue Errno::EACCES => e
raise DirectoryNotWritableError.new(self, src.relative_path_from(path), dst, e)
rescue SystemCallError => e
raise LinkError.new(self, src.relative_path_from(path), dst, e)
end
protected
# symlinks the contents of path+relative_dir recursively into #{HOMEBREW_PREFIX}/relative_dir
def link_dir relative_dir, mode
root = path+relative_dir
return unless root.exist?
root.find do |src|
next if src == root
dst = HOMEBREW_PREFIX + src.relative_path_from(path)
dst.extend ObserverPathnameExtension
if src.symlink? || src.file?
Find.prune if File.basename(src) == '.DS_Store'
# Don't link pyc files because Python overwrites these cached object
# files and next time brew wants to link, the pyc file is in the way.
if src.extname == '.pyc' && src.to_s =~ /site-packages/
Find.prune
end
case yield src.relative_path_from(root)
when :skip_file, nil
Find.prune
when :info
next if File.basename(src) == 'dir' # skip historical local 'dir' files
make_relative_symlink dst, src, mode
dst.install_info
else
make_relative_symlink dst, src, mode
end
elsif src.directory?
# if the dst dir already exists, then great! walk the rest of the tree tho
next if dst.directory? and not dst.symlink?
# no need to put .app bundles in the path, the user can just use
# spotlight, or the open command and actual mac apps use an equivalent
Find.prune if src.extname == '.app'
case yield src.relative_path_from(root)
when :skip_dir
Find.prune
when :mkpath
dst.mkpath unless resolve_any_conflicts(dst, mode)
else
unless resolve_any_conflicts(dst, mode)
make_relative_symlink dst, src, mode
Find.prune
end
end
end
end
end
end

View File

@ -0,0 +1,202 @@
class Keg
PREFIX_PLACEHOLDER = "@@HOMEBREW_PREFIX@@".freeze
CELLAR_PLACEHOLDER = "@@HOMEBREW_CELLAR@@".freeze
def fix_install_names options={}
mach_o_files.each do |file|
file.ensure_writable do
change_dylib_id(dylib_id_for(file, options), file) if file.dylib?
each_install_name_for(file) do |bad_name|
# Don't fix absolute paths unless they are rooted in the build directory
next if bad_name.start_with? '/' and not bad_name.start_with? HOMEBREW_TEMP.to_s
new_name = fixed_name(file, bad_name)
change_install_name(bad_name, new_name, file) unless new_name == bad_name
end
end
end
end
def relocate_install_names old_prefix, new_prefix, old_cellar, new_cellar, options={}
mach_o_files.each do |file|
file.ensure_writable do
if file.dylib?
id = dylib_id_for(file, options).sub(old_prefix, new_prefix)
change_dylib_id(id, file)
end
each_install_name_for(file) do |old_name|
if old_name.start_with? old_cellar
new_name = old_name.sub(old_cellar, new_cellar)
elsif old_name.start_with? old_prefix
new_name = old_name.sub(old_prefix, new_prefix)
end
change_install_name(old_name, new_name, file) if new_name
end
end
end
files = pkgconfig_files | libtool_files | script_files
files.group_by { |f| f.stat.ino }.each_value do |first, *rest|
s = first.open("rb", &:read)
changed = s.gsub!(old_cellar, new_cellar)
changed = s.gsub!(old_prefix, new_prefix) || changed
begin
first.atomic_write(s)
rescue SystemCallError
first.ensure_writable do
first.open("wb") { |f| f.write(s) }
end
else
rest.each { |file| FileUtils.ln(first, file, :force => true) }
end if changed
end
end
def change_dylib_id(id, file)
puts "Changing dylib ID of #{file}\n from #{file.dylib_id}\n to #{id}" if ARGV.debug?
install_name_tool("-id", id, file)
end
def change_install_name(old, new, file)
puts "Changing install name in #{file}\n from #{old}\n to #{new}" if ARGV.debug?
install_name_tool("-change", old, new, file)
end
# Detects the C++ dynamic libraries in place, scanning the dynamic links
# of the files within the keg. This searches only libs contained within
# lib/, and ignores binaries and other mach-o objects
# Note that this doesn't attempt to distinguish between libstdc++ versions,
# for instance between Apple libstdc++ and GNU libstdc++
def detect_cxx_stdlibs(options={})
options = { :skip_executables => false }.merge(options)
skip_executables = options[:skip_executables]
results = Set.new
mach_o_files.each do |file|
next if file.mach_o_executable? && skip_executables
dylibs = file.dynamically_linked_libraries
results << :libcxx unless dylibs.grep(/libc\+\+.+\.dylib/).empty?
results << :libstdcxx unless dylibs.grep(/libstdc\+\+.+\.dylib/).empty?
end
results.to_a
end
def each_unique_file_matching string
Utils.popen_read("/usr/bin/fgrep", "-lr", string, to_s) do |io|
hardlinks = Set.new
until io.eof?
file = Pathname.new(io.readline.chomp)
next if file.symlink?
yield file if hardlinks.add? file.stat.ino
end
end
end
def install_name_tool(*args)
tool = MacOS.locate("install_name_tool")
system(tool, *args) or raise ErrorDuringExecution.new(tool, args)
end
# If file is a dylib or bundle itself, look for the dylib named by
# bad_name relative to the lib directory, so that we can skip the more
# expensive recursive search if possible.
def fixed_name(file, bad_name)
if bad_name.start_with? PREFIX_PLACEHOLDER
bad_name.sub(PREFIX_PLACEHOLDER, HOMEBREW_PREFIX.to_s)
elsif bad_name.start_with? CELLAR_PLACEHOLDER
bad_name.sub(CELLAR_PLACEHOLDER, HOMEBREW_CELLAR.to_s)
elsif (file.dylib? || file.mach_o_bundle?) && (file.parent + bad_name).exist?
"@loader_path/#{bad_name}"
elsif file.mach_o_executable? && (lib + bad_name).exist?
"#{lib}/#{bad_name}"
elsif (abs_name = find_dylib(Pathname.new(bad_name).basename)) && abs_name.exist?
abs_name.to_s
else
opoo "Could not fix #{bad_name} in #{file}"
bad_name
end
end
def lib
path.join("lib")
end
def each_install_name_for file, &block
dylibs = file.dynamically_linked_libraries
dylibs.reject! { |fn| fn =~ /^@(loader_|executable_|r)path/ }
dylibs.each(&block)
end
def dylib_id_for(file, options)
# The new dylib ID should have the same basename as the old dylib ID, not
# the basename of the file itself.
basename = File.basename(file.dylib_id)
relative_dirname = file.dirname.relative_path_from(path)
shortpath = HOMEBREW_PREFIX.join(relative_dirname, basename)
if shortpath.exist? and not options[:keg_only]
shortpath.to_s
else
opt_record.join(relative_dirname, basename).to_s
end
end
def find_dylib name
lib.find { |pn| break pn if pn.basename == name }
end
def mach_o_files
mach_o_files = []
path.find do |pn|
next if pn.symlink? or pn.directory?
mach_o_files << pn if pn.dylib? or pn.mach_o_bundle? or pn.mach_o_executable?
end
mach_o_files
end
def script_files
script_files = []
# find all files with shebangs
find do |pn|
next if pn.symlink? or pn.directory?
script_files << pn if pn.text_executable?
end
script_files
end
def pkgconfig_files
pkgconfig_files = []
%w[lib share].each do |dir|
pcdir = path.join(dir, "pkgconfig")
pcdir.find do |pn|
next if pn.symlink? or pn.directory? or pn.extname != '.pc'
pkgconfig_files << pn
end if pcdir.directory?
end
pkgconfig_files
end
def libtool_files
libtool_files = []
# find .la files, which are stored in lib/
lib.find do |pn|
next if pn.symlink? or pn.directory? or pn.extname != '.la'
libtool_files << pn
end if lib.directory?
libtool_files
end
end

View File

@ -0,0 +1,13 @@
require "resource"
module Language
module Go
# Given a set of resources, stages them to a gopath for
# building go software.
# The resource names should be the import name of the package,
# e.g. `resource "github.com/foo/bar"`
def self.stage_deps resources, target
resources.grep(Resource::Go) { |resource| resource.stage(target) }
end
end
end

View File

@ -0,0 +1,68 @@
module Language
module Haskell
# module for formulas using cabal-install as build tool
module Cabal
def cabal_sandbox
pwd = Pathname.pwd
# force cabal to put its stuff here instead of the home directory by
# pretending the home is here. This also avoid to deal with many options
# to configure cabal. Note this is also useful with cabal sandbox to
# avoid touching ~/.cabal
home = ENV["HOME"]
ENV["HOME"] = pwd
# use cabal's sandbox feature if available
cabal_version = `cabal --version`[/[0-9.]+/].split('.').collect(&:to_i)
if (cabal_version <=> [1, 20]) > -1
system "cabal", "sandbox", "init"
cabal_sandbox_bin = pwd/".cabal-sandbox/bin"
else
# no or broken sandbox feature - just use the HOME trick
cabal_sandbox_bin = pwd/".cabal/bin"
end
# cabal may build useful tools that should be found in the PATH
mkdir_p cabal_sandbox_bin
path = ENV["PATH"]
ENV.prepend_path 'PATH', cabal_sandbox_bin
# update cabal package database
system "cabal", "update"
yield
# restore the environment
if (cabal_version <=> [1, 20]) > -1
system "cabal", "sandbox", "delete"
end
ENV["HOME"] = home
ENV["PATH"] = path
end
def cabal_install(*opts)
system "cabal", "install", "--jobs=#{ENV.make_jobs}", *opts
end
# install the tools passed in parameter and remove the packages that where
# used so they won't be in the way of the dependency solver for the main
# package. The tools are installed sequentially in order to make possible
# to install several tools that depends on each other
def cabal_install_tools(*opts)
opts.each {|t| cabal_install t}
rm_rf Dir[".cabal*/*packages.conf.d/"]
end
# remove the development files from the lib directory. cabal-install should
# be used instead to install haskell packages
def cabal_clean_lib
# a better approach may be needed here
rm_rf lib
end
def install_cabal_package
cabal_sandbox do
# the dependencies are built first and installed locally, and only the
# current package is actually installed in the destination dir
cabal_install "--only-dependencies"
cabal_install "--prefix=#{prefix}"
end
cabal_clean_lib
end
end
end
end

View File

@ -0,0 +1,26 @@
require "utils.rb"
module Language
module Python
def self.major_minor_version python
version = /\d\.\d/.match `#{python} --version 2>&1`
return unless version
Version.new(version.to_s)
end
def self.each_python build, &block
original_pythonpath = ENV["PYTHONPATH"]
["python", "python3"].each do |python|
next if build.without? python
version = self.major_minor_version python
ENV["PYTHONPATH"] = if Formulary.factory(python).installed?
nil
else
"#{HOMEBREW_PREFIX}/lib/python#{version}/site-packages"
end
block.call python, version if block
end
ENV["PYTHONPATH"] = original_pythonpath
end
end
end

View File

@ -0,0 +1,186 @@
module ArchitectureListExtension
def fat?
length > 1
end
def intel_universal?
intersects_all?(Hardware::CPU::INTEL_32BIT_ARCHS, Hardware::CPU::INTEL_64BIT_ARCHS)
end
def ppc_universal?
intersects_all?(Hardware::CPU::PPC_32BIT_ARCHS, Hardware::CPU::PPC_64BIT_ARCHS)
end
# Old-style 32-bit PPC/Intel universal, e.g. ppc7400 and i386
def cross_universal?
intersects_all?(Hardware::CPU::PPC_32BIT_ARCHS, Hardware::CPU::INTEL_32BIT_ARCHS)
end
def universal?
intel_universal? || ppc_universal? || cross_universal?
end
def ppc?
(Hardware::CPU::PPC_32BIT_ARCHS+Hardware::CPU::PPC_64BIT_ARCHS).any? {|a| self.include? a}
end
def remove_ppc!
(Hardware::CPU::PPC_32BIT_ARCHS+Hardware::CPU::PPC_64BIT_ARCHS).each {|a| self.delete a}
end
def as_arch_flags
self.collect{ |a| "-arch #{a}" }.join(' ')
end
def as_cmake_arch_flags
self.join(';')
end
protected
def intersects_all?(*set)
set.all? do |archset|
archset.any? {|a| self.include? a}
end
end
end
module MachO
OTOOL_RX = /\t(.*) \(compatibility version (?:\d+\.)*\d+, current version (?:\d+\.)*\d+\)/
# Mach-O binary methods, see:
# /usr/include/mach-o/loader.h
# /usr/include/mach-o/fat.h
def mach_data
@mach_data ||= begin
offsets = []
mach_data = []
header = read(8).unpack("N2")
case header[0]
when 0xcafebabe # universal
header[1].times do |i|
# header[1] is the number of struct fat_arch in the file.
# Each struct fat_arch is 20 bytes, and the 'offset' member
# begins 8 bytes into the struct, with an additional 8 byte
# offset due to the struct fat_header at the beginning of
# the file.
offsets << read(4, 20*i + 16).unpack("N")[0]
end
when 0xcefaedfe, 0xcffaedfe, 0xfeedface, 0xfeedfacf # Single arch
offsets << 0
when 0x7f454c46 # ELF
mach_data << { :arch => :x86_64, :type => :executable }
else
raise "Not a Mach-O binary."
end
offsets.each do |offset|
arch = case read(8, offset).unpack("N2")
when [0xcefaedfe, 0x07000000] then :i386
when [0xcffaedfe, 0x07000001] then :x86_64
when [0xfeedface, 0x00000012] then :ppc7400
when [0xfeedfacf, 0x01000012] then :ppc64
else :dunno
end
type = case read(4, offset + 12).unpack("N")[0]
when 0x00000002, 0x02000000 then :executable
when 0x00000006, 0x06000000 then :dylib
when 0x00000008, 0x08000000 then :bundle
else :dunno
end
mach_data << { :arch => arch, :type => type }
end
mach_data
rescue
[]
end
end
def archs
mach_data.map{ |m| m.fetch :arch }.extend(ArchitectureListExtension)
end
def arch
case archs.length
when 0 then :dunno
when 1 then archs.first
else :universal
end
end
def universal?
arch == :universal
end
def i386?
arch == :i386
end
def x86_64?
arch == :x86_64
end
def ppc7400?
arch == :ppc7400
end
def ppc64?
arch == :ppc64
end
def dylib?
mach_data.any? { |m| m.fetch(:type) == :dylib }
end
def mach_o_executable?
mach_data.any? { |m| m.fetch(:type) == :executable }
end
def mach_o_bundle?
mach_data.any? { |m| m.fetch(:type) == :bundle }
end
class Metadata
attr_reader :path, :dylib_id, :dylibs
def initialize(path)
@path = path
@dylib_id, @dylibs = parse_otool_L_output
end
def parse_otool_L_output
ENV["HOMEBREW_MACH_O_FILE"] = path.expand_path.to_s
libs = `#{MacOS.locate("otool")} -L "$HOMEBREW_MACH_O_FILE"`.split("\n")
libs.shift # first line is the filename
id = libs.shift[OTOOL_RX, 1] if path.dylib?
libs.map! { |lib| lib[OTOOL_RX, 1] }.compact!
return id, libs
ensure
ENV.delete "HOMEBREW_MACH_O_FILE"
end
end
def mach_metadata
@mach_metadata ||= Metadata.new(self)
end
# Returns an array containing all dynamically-linked libraries, based on the
# output of otool. This returns the install names, so these are not guaranteed
# to be absolute paths.
# Returns an empty array both for software that links against no libraries,
# and for non-mach objects.
def dynamically_linked_libraries
mach_metadata.dylibs
end
def dylib_id
mach_metadata.dylib_id
end
end

View File

@ -0,0 +1,19 @@
class Metafiles
EXTENSIONS = %w[.md .html .rtf .txt]
BASENAMES = %w[
about authors changelog changes copying copyright history license licence
news notes notice readme todo
]
def self.list?(file)
return false if %w[.DS_Store INSTALL_RECEIPT.json].include?(file)
!copy?(file)
end
def self.copy?(file)
file = file.downcase
ext = File.extname(file)
file = File.basename(file, ext) if EXTENSIONS.include?(ext)
BASENAMES.include?(file)
end
end

View File

@ -0,0 +1,92 @@
require 'set'
class Option
attr_reader :name, :description, :flag
def initialize(name, description="")
@name = name
@flag = "--#{name}"
@description = description
end
def to_s
flag
end
def <=>(other)
return unless Option === other
name <=> other.name
end
def ==(other)
instance_of?(other.class) && name == other.name
end
alias_method :eql?, :==
def hash
name.hash
end
def inspect
"#<#{self.class.name}: #{flag.inspect}>"
end
end
class Options
include Enumerable
def self.create(array)
new array.map { |e| Option.new(e[/^--(.+)$/, 1] || e) }
end
def initialize(*args)
@options = Set.new(*args)
end
def each(*args, &block)
@options.each(*args, &block)
end
def <<(o)
@options << o
self
end
def +(o)
self.class.new(@options + o)
end
def -(o)
self.class.new(@options - o)
end
def &(o)
self.class.new(@options & o)
end
def |(o)
self.class.new(@options | o)
end
def *(arg)
@options.to_a * arg
end
def empty?
@options.empty?
end
def as_flags
map(&:flag)
end
def include?(o)
any? { |opt| opt == o || opt.name == o || opt.flag == o }
end
alias_method :to_ary, :to_a
def inspect
"#<#{self.class.name}: #{to_a.inspect}>"
end
end

View File

@ -0,0 +1,19 @@
module OS
def self.mac?
/darwin/i === RUBY_PLATFORM
end
def self.linux?
/linux/i === RUBY_PLATFORM
end
if OS.mac?
ISSUES_URL = "https://github.com/Homebrew/homebrew/wiki/troubleshooting"
PATH_OPEN = "/usr/bin/open"
elsif OS.linux?
ISSUES_URL = "https://github.com/Homebrew/linuxbrew/wiki/troubleshooting"
PATH_OPEN = "xdg-open"
else
raise "Unknown operating system"
end
end

View File

@ -0,0 +1,53 @@
module LinuxCPUs
OPTIMIZATION_FLAGS = {
:penryn => '-march=core2 -msse4.1',
:core2 => '-march=core2',
:core => '-march=prescott',
}.freeze
def optimization_flags; OPTIMIZATION_FLAGS; end
# Linux supports x86 only, and universal archs do not apply
def arch_32_bit; :i386; end
def arch_64_bit; :x86_64; end
def universal_archs; [].extend ArchitectureListExtension; end
def cpuinfo
@cpuinfo ||= File.read("/proc/cpuinfo")
end
def type
@type ||= if cpuinfo =~ /Intel|AMD/
:intel
else
:dunno
end
end
def family
cpuinfo[/^cpu family\s*: ([0-9]+)/, 1].to_i
end
alias_method :intel_family, :family
def cores
cpuinfo.scan(/^processor/).size
end
def flags
@flags ||= cpuinfo[/^flags.*/, 0].split
end
# Compatibility with Mac method, which returns lowercase symbols
# instead of strings
def features
@features ||= flags[1..-1].map(&:intern)
end
%w[aes altivec avx avx2 lm sse3 ssse3 sse4 sse4_2].each { |flag|
define_method(flag + "?") { flags.include? flag }
}
alias_method :is_64_bit?, :lm?
def bits
is_64_bit? ? 64 : 32
end
end

View File

@ -0,0 +1,253 @@
require 'hardware'
require 'os/mac/version'
require 'os/mac/xcode'
require 'os/mac/xquartz'
module OS
module Mac
extend self
::MacOS = self # compatibility
# This can be compared to numerics, strings, or symbols
# using the standard Ruby Comparable methods.
def version
@version ||= Version.new(MACOS_VERSION)
end
def cat
version.to_sym
end
def locate tool
# Don't call tools (cc, make, strip, etc.) directly!
# Give the name of the binary you look for as a string to this method
# in order to get the full path back as a Pathname.
(@locate ||= {}).fetch(tool) do |key|
@locate[key] = if File.executable?(path = "/usr/bin/#{tool}")
Pathname.new path
# Homebrew GCCs most frequently; much faster to check this before xcrun
elsif File.executable?(path = "#{HOMEBREW_PREFIX}/bin/#{tool}")
Pathname.new path
else
path = `/usr/bin/xcrun -no-cache -find #{tool} 2>/dev/null`.chomp
Pathname.new(path) if File.executable?(path)
end
end
end
def active_developer_dir
@active_developer_dir ||= `xcode-select -print-path 2>/dev/null`.strip
end
def sdk_path(v = version)
(@sdk_path ||= {}).fetch(v.to_s) do |key|
opts = []
# First query Xcode itself
opts << `#{locate('xcodebuild')} -version -sdk macosx#{v} Path 2>/dev/null`.chomp
# Xcode.prefix is pretty smart, so lets look inside to find the sdk
opts << "#{Xcode.prefix}/Platforms/MacOSX.platform/Developer/SDKs/MacOSX#{v}.sdk"
# Xcode < 4.3 style
opts << "/Developer/SDKs/MacOSX#{v}.sdk"
@sdk_path[key] = opts.map { |a| Pathname.new(a) }.detect(&:directory?)
end
end
def default_cc
cc = locate 'cc'
cc.realpath.basename.to_s rescue nil
end
def default_compiler
case default_cc
when /^gcc-4.0/ then :gcc_4_0
when /^gcc/ then :gcc
when /^llvm/ then :llvm
when "clang" then :clang
else
# guess :(
if Xcode.version >= "4.3"
:clang
elsif Xcode.version >= "4.2"
:llvm
else
:gcc
end
end
end
def default_cxx_stdlib
version >= :mavericks ? :libcxx : :libstdcxx
end
def gcc_40_build_version
@gcc_40_build_version ||=
if (path = locate("gcc-4.0"))
%x{#{path} --version}[/build (\d{4,})/, 1].to_i
end
end
alias_method :gcc_4_0_build_version, :gcc_40_build_version
def gcc_42_build_version
@gcc_42_build_version ||=
begin
gcc = MacOS.locate("gcc-4.2") || HOMEBREW_PREFIX.join("opt/apple-gcc42/bin/gcc-4.2")
if gcc.exist? && gcc.realpath.basename.to_s !~ /^llvm/
%x{#{gcc} --version}[/build (\d{4,})/, 1].to_i
end
end
end
alias_method :gcc_build_version, :gcc_42_build_version
def llvm_build_version
@llvm_build_version ||=
if (path = locate("llvm-gcc")) && path.realpath.basename.to_s !~ /^clang/
%x{#{path} --version}[/LLVM build (\d{4,})/, 1].to_i
end
end
def clang_version
@clang_version ||=
if (path = locate("clang"))
%x{#{path} --version}[/(?:clang|LLVM) version (\d\.\d)/, 1]
end
end
def clang_build_version
@clang_build_version ||=
if (path = locate("clang"))
%x{#{path} --version}[%r[clang-(\d{2,})], 1].to_i
end
end
def non_apple_gcc_version(cc)
(@non_apple_gcc_version ||= {}).fetch(cc) do
path = HOMEBREW_PREFIX.join("opt", "gcc", "bin", cc)
path = locate(cc) unless path.exist?
version = %x{#{path} --version}[/gcc(?:-\d\.\d \(.+\))? (\d\.\d\.\d)/, 1] if path
@non_apple_gcc_version[cc] = version
end
end
def clear_version_cache
@gcc_40_build_version = @gcc_42_build_version = @llvm_build_version = nil
@clang_version = @clang_build_version = nil
@non_apple_gcc_version = {}
end
# See these issues for some history:
# http://github.com/Homebrew/homebrew/issues/13
# http://github.com/Homebrew/homebrew/issues/41
# http://github.com/Homebrew/homebrew/issues/48
def macports_or_fink
paths = []
# First look in the path because MacPorts is relocatable and Fink
# may become relocatable in the future.
%w{port fink}.each do |ponk|
path = which(ponk)
paths << path unless path.nil?
end
# Look in the standard locations, because even if port or fink are
# not in the path they can still break builds if the build scripts
# have these paths baked in.
%w{/sw/bin/fink /opt/local/bin/port}.each do |ponk|
path = Pathname.new(ponk)
paths << path if path.exist?
end
# Finally, some users make their MacPorts or Fink directorie
# read-only in order to try out Homebrew, but this doens't work as
# some build scripts error out when trying to read from these now
# unreadable paths.
%w{/sw /opt/local}.map { |p| Pathname.new(p) }.each do |path|
paths << path if path.exist? && !path.readable?
end
paths.uniq
end
def prefer_64_bit?
Hardware::CPU.is_64_bit? and version > :leopard
end
def preferred_arch
if prefer_64_bit?
Hardware::CPU.arch_64_bit
else
Hardware::CPU.arch_32_bit
end
end
STANDARD_COMPILERS = {
"2.5" => { :gcc_40_build => 5370 },
"3.1.4" => { :gcc_40_build => 5493, :gcc_42_build => 5577 },
"3.2.6" => { :gcc_40_build => 5494, :gcc_42_build => 5666, :llvm_build => 2335, :clang => "1.7", :clang_build => 77 },
"4.0" => { :gcc_40_build => 5494, :gcc_42_build => 5666, :llvm_build => 2335, :clang => "2.0", :clang_build => 137 },
"4.0.1" => { :gcc_40_build => 5494, :gcc_42_build => 5666, :llvm_build => 2335, :clang => "2.0", :clang_build => 137 },
"4.0.2" => { :gcc_40_build => 5494, :gcc_42_build => 5666, :llvm_build => 2335, :clang => "2.0", :clang_build => 137 },
"4.2" => { :llvm_build => 2336, :clang => "3.0", :clang_build => 211 },
"4.3" => { :llvm_build => 2336, :clang => "3.1", :clang_build => 318 },
"4.3.1" => { :llvm_build => 2336, :clang => "3.1", :clang_build => 318 },
"4.3.2" => { :llvm_build => 2336, :clang => "3.1", :clang_build => 318 },
"4.3.3" => { :llvm_build => 2336, :clang => "3.1", :clang_build => 318 },
"4.4" => { :llvm_build => 2336, :clang => "4.0", :clang_build => 421 },
"4.4.1" => { :llvm_build => 2336, :clang => "4.0", :clang_build => 421 },
"4.5" => { :llvm_build => 2336, :clang => "4.1", :clang_build => 421 },
"4.5.1" => { :llvm_build => 2336, :clang => "4.1", :clang_build => 421 },
"4.5.2" => { :llvm_build => 2336, :clang => "4.1", :clang_build => 421 },
"4.6" => { :llvm_build => 2336, :clang => "4.2", :clang_build => 425 },
"4.6.1" => { :llvm_build => 2336, :clang => "4.2", :clang_build => 425 },
"4.6.2" => { :llvm_build => 2336, :clang => "4.2", :clang_build => 425 },
"4.6.3" => { :llvm_build => 2336, :clang => "4.2", :clang_build => 425 },
"5.0" => { :clang => "5.0", :clang_build => 500 },
"5.0.1" => { :clang => "5.0", :clang_build => 500 },
"5.0.2" => { :clang => "5.0", :clang_build => 500 },
"5.1" => { :clang => "5.1", :clang_build => 503 },
"5.1.1" => { :clang => "5.1", :clang_build => 503 },
"6.0" => { :clang => "6.0", :clang_build => 600 },
"6.0.1" => { :clang => "6.0", :clang_build => 600 },
"6.1" => { :clang => "6.0", :clang_build => 600 },
}
def compilers_standard?
STANDARD_COMPILERS.fetch(Xcode.version.to_s).all? do |method, build|
send(:"#{method}_version") == build
end
rescue IndexError
onoe <<-EOS.undent
Homebrew doesn't know what compiler versions ship with your version
of Xcode (#{Xcode.version}). Please `brew update` and if that doesn't help, file
an issue with the output of `brew --config`:
https://github.com/Homebrew/homebrew/issues
Note that we only track stable, released versions of Xcode.
Thanks!
EOS
end
def app_with_bundle_id(*ids)
path = mdfind(*ids).first
Pathname.new(path) unless path.nil? or path.empty?
end
def mdfind(*ids)
return [] unless OS.mac?
(@mdfind ||= {}).fetch(ids) do
@mdfind[ids] = Utils.popen_read("/usr/bin/mdfind", mdfind_query(*ids)).split("\n")
end
end
def pkgutil_info(id)
(@pkginfo ||= {}).fetch(id) do |key|
@pkginfo[key] = Utils.popen_read("/usr/sbin/pkgutil", "--pkg-info", key).strip
end
end
def mdfind_query(*ids)
ids.map! { |id| "kMDItemCFBundleIdentifier == #{id}" }.join(" || ")
end
end
end

View File

@ -0,0 +1,148 @@
require 'mach'
module MacCPUs
OPTIMIZATION_FLAGS = {
:penryn => '-march=core2 -msse4.1',
:core2 => '-march=core2',
:core => '-march=prescott',
:g3 => '-mcpu=750',
:g4 => '-mcpu=7400',
:g4e => '-mcpu=7450',
:g5 => '-mcpu=970'
}.freeze
def optimization_flags; OPTIMIZATION_FLAGS; end
# These methods use info spewed out by sysctl.
# Look in <mach/machine.h> for decoding info.
def type
@type ||= `/usr/sbin/sysctl -n hw.cputype`.to_i
case @type
when 7
:intel
when 18
:ppc
else
:dunno
end
end
def family
if intel?
case @intel_family ||= `/usr/sbin/sysctl -n hw.cpufamily`.to_i
when 0x73d67300 # Yonah: Core Solo/Duo
:core
when 0x426f69ef # Merom: Core 2 Duo
:core2
when 0x78ea4fbc # Penryn
:penryn
when 0x6b5a4cd2 # Nehalem
:nehalem
when 0x573B5EEC # Arrandale
:arrandale
when 0x5490B78C # Sandy Bridge
:sandybridge
when 0x1F65E835 # Ivy Bridge
:ivybridge
when 0x10B282DC # Haswell
:haswell
else
:dunno
end
elsif ppc?
case @ppc_family ||= `/usr/sbin/sysctl -n hw.cpusubtype`.to_i
when 9
:g3 # PowerPC 750
when 10
:g4 # PowerPC 7400
when 11
:g4e # PowerPC 7450
when 100
# This is the only 64-bit PPC CPU type, so it's useful
# to distinguish in `brew config` output and in bottle tags
MacOS.prefer_64_bit? ? :g5_64 : :g5 # PowerPC 970
else
:dunno
end
end
end
def extmodel
@extmodel ||= `/usr/sbin/sysctl -n machdep.cpu.extmodel`.to_i
end
def cores
@cores ||= `/usr/sbin/sysctl -n hw.ncpu`.to_i
end
def bits
@bits ||= sysctl_bool("hw.cpu64bit_capable") ? 64 : 32
end
def arch_32_bit
intel? ? :i386 : :ppc
end
def arch_64_bit
intel? ? :x86_64 : :ppc64
end
# Returns an array that's been extended with ArchitectureListExtension,
# which provides helpers like #as_arch_flags and #as_cmake_arch_flags.
def universal_archs
# Building 64-bit is a no-go on Tiger, and pretty hit or miss on Leopard.
# Don't even try unless Tigerbrew's experimental 64-bit Leopard support is enabled.
if MacOS.version <= :leopard and !MacOS.prefer_64_bit?
[arch_32_bit].extend ArchitectureListExtension
else
[arch_32_bit, arch_64_bit].extend ArchitectureListExtension
end
end
def features
@features ||= `/usr/sbin/sysctl -n machdep.cpu.features`.split(" ").map do |s|
s.downcase.intern
end
end
def aes?
sysctl_bool('hw.optional.aes')
end
def altivec?
sysctl_bool('hw.optional.altivec')
end
def avx?
sysctl_bool('hw.optional.avx1_0')
end
def avx2?
sysctl_bool('hw.optional.avx2_0')
end
def sse3?
sysctl_bool('hw.optional.sse3')
end
def ssse3?
sysctl_bool('hw.optional.supplementalsse3')
end
def sse4?
sysctl_bool('hw.optional.sse4_1')
end
def sse4_2?
sysctl_bool('hw.optional.sse4_2')
end
protected
def sysctl_bool(property)
(@properties ||= {}).fetch(property) do
result = Utils.popen_read("/usr/sbin/sysctl", "-n", property, &:gets).to_i
# sysctl call succeded and printed 1
@properties[property] = $?.success? && result == 1
end
end
end

View File

@ -0,0 +1,44 @@
require 'version'
module OS
module Mac
class Version < ::Version
SYMBOLS = {
:yosemite => '10.10',
:mavericks => '10.9',
:mountain_lion => '10.8',
:lion => '10.7',
:snow_leopard => '10.6',
:leopard => '10.5',
:tiger => '10.4',
}
def self.from_symbol(sym)
str = SYMBOLS.fetch(sym) do
raise ArgumentError, "unknown version #{sym.inspect}"
end
new(str)
end
def initialize(*args)
super
@comparison_cache = {}
end
def <=>(other)
@comparison_cache.fetch(other) do
v = SYMBOLS.fetch(other) { other.to_s }
@comparison_cache[other] = super(Version.new(v))
end
end
def to_sym
SYMBOLS.invert.fetch(@version) { :dunno }
end
def pretty_name
to_sym.to_s.split('_').map(&:capitalize).join(' ')
end
end
end
end

View File

@ -0,0 +1,197 @@
module OS
module Mac
module Xcode
extend self
V4_BUNDLE_ID = "com.apple.dt.Xcode"
V3_BUNDLE_ID = "com.apple.Xcode"
def latest_version
case MacOS.version
when "10.4" then "2.5"
when "10.5" then "3.1.4"
when "10.6" then "3.2.6"
when "10.7" then "4.6.3"
when "10.8" then "5.1.1"
when "10.9" then "6.0.1"
when "10.10" then "6.1"
else
# Default to newest known version of Xcode for unreleased OSX versions.
if MacOS.version > "10.10"
"6.1"
else
raise "Mac OS X '#{MacOS.version}' is invalid"
end
end
end
def outdated?
version < latest_version
end
def without_clt?
installed? && version >= "4.3" && !MacOS::CLT.installed?
end
def prefix
@prefix ||=
begin
dir = MacOS.active_developer_dir
if dir.empty? || dir == CLT::MAVERICKS_PKG_PATH || !File.directory?(dir)
path = bundle_path
path.join("Contents", "Developer") if path
else
Pathname.new(dir)
end
end
end
def toolchain_path
Pathname.new("#{prefix}/Toolchains/XcodeDefault.xctoolchain") if installed? && version >= "4.3"
end
# Ask Spotlight where Xcode is. If the user didn't install the
# helper tools and installed Xcode in a non-conventional place, this
# is our only option. See: http://superuser.com/questions/390757
def bundle_path
MacOS.app_with_bundle_id(V4_BUNDLE_ID, V3_BUNDLE_ID)
end
def installed?
not prefix.nil?
end
def version
# may return a version string
# that is guessed based on the compiler, so do not
# use it in order to check if Xcode is installed.
@version ||= uncached_version
end
def uncached_version
# This is a separate function as you can't cache the value out of a block
# if return is used in the middle, which we do many times in here.
return "0" unless OS.mac?
%W[#{prefix}/usr/bin/xcodebuild #{which("xcodebuild")}].uniq.each do |path|
if File.file? path
`#{path} -version 2>/dev/null` =~ /Xcode (\d(\.\d)*)/
return $1 if $1
end
end
# The remaining logic provides a fake Xcode version for CLT-only
# systems. This behavior only exists because Homebrew used to assume
# Xcode.version would always be non-nil. This is deprecated, and will
# be removed in a future version. To remain compatible, guard usage of
# Xcode.version with an Xcode.installed? check.
case MacOS.llvm_build_version.to_i
when 1..2063 then "3.1.0"
when 2064..2065 then "3.1.4"
when 2366..2325
# we have no data for this range so we are guessing
"3.2.0"
when 2326
# also applies to "3.2.3"
"3.2.4"
when 2327..2333 then "3.2.5"
when 2335
# this build number applies to 3.2.6, 4.0 and 4.1
# https://github.com/Homebrew/homebrew/wiki/Xcode
"4.0"
else
case (MacOS.clang_version.to_f * 10).to_i
when 0 then "dunno"
when 1..14 then "3.2.2"
when 15 then "3.2.4"
when 16 then "3.2.5"
when 17..20 then "4.0"
when 21 then "4.1"
when 22..30 then "4.2"
when 31 then "4.3"
when 40 then "4.4"
when 41 then "4.5"
when 42 then "4.6"
when 50 then "5.0"
when 51 then "5.1"
when 60 then "6.0"
else "6.0"
end
end
end
def provides_autotools?
version < "4.3"
end
def provides_gcc?
version < "4.3"
end
def provides_cvs?
version < "5.0"
end
def default_prefix?
if version < "4.3"
%r{^/Developer} === prefix
else
%r{^/Applications/Xcode.app} === prefix
end
end
end
module CLT
extend self
STANDALONE_PKG_ID = "com.apple.pkg.DeveloperToolsCLILeo"
FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI"
MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables"
# Used for Yosemite and Mavericks CLT since June 2014.
MAVERICKS_NEW_PKG_ID = "com.apple.pkg.CLTools_Base"
MAVERICKS_PKG_PATH = "/Library/Developer/CommandLineTools"
# Returns true even if outdated tools are installed, e.g.
# tools from Xcode 4.x on 10.9
def installed?
!!detect_version
end
def latest_version
case MacOS.version
when "10.10" then "600.0.54"
when "10.9" then "600.0.51"
when "10.8" then "503.0.40"
else
"425.0.28"
end
end
def outdated?
if MacOS.version >= :mavericks
version = `#{MAVERICKS_PKG_PATH}/usr/bin/clang --version`
else
version = `/usr/bin/clang --version`
end
version = version[%r{clang-(\d+\.\d+\.\d+)}, 1] || "0"
version < latest_version
end
# Version string (a pretty long one) of the CLT package.
# Note, that different ways to install the CLTs lead to different
# version numbers.
def version
@version ||= detect_version
end
def detect_version
[MAVERICKS_NEW_PKG_ID, MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID].find do |id|
version = MacOS.pkgutil_info(id)[/version: (.+)$/, 1]
return version if version
end
end
end
end
end

View File

@ -0,0 +1,137 @@
module OS
module Mac
X11 = XQuartz = Module.new
module XQuartz
extend self
FORGE_BUNDLE_ID = "org.macosforge.xquartz.X11"
APPLE_BUNDLE_ID = "org.x.X11"
FORGE_PKG_ID = "org.macosforge.xquartz.pkg"
PKGINFO_VERSION_MAP = {
"2.6.34" => "2.6.3",
"2.7.4" => "2.7.0",
"2.7.14" => "2.7.1",
"2.7.28" => "2.7.2",
"2.7.32" => "2.7.3",
"2.7.43" => "2.7.4",
"2.7.50" => "2.7.5_rc1",
"2.7.51" => "2.7.5_rc2",
"2.7.52" => "2.7.5_rc3",
"2.7.53" => "2.7.5_rc4",
"2.7.54" => "2.7.5",
"2.7.61" => "2.7.6",
"2.7.73" => "2.7.7",
}.freeze
# This returns the version number of XQuartz, not of the upstream X.org.
# The X11.app distributed by Apple is also XQuartz, and therefore covered
# by this method.
def version
@version ||= detect_version
end
def detect_version
if (path = bundle_path) && path.exist? && (version = version_from_mdls(path))
version
elsif prefix.to_s == "/usr/X11"
guess_system_version
else
version_from_pkgutil
end
end
# http://xquartz.macosforge.org/trac/wiki
# http://xquartz.macosforge.org/trac/wiki/Releases
def latest_version
case MacOS.version
when "10.5"
"2.6.3"
else
"2.7.7"
end
end
def bundle_path
MacOS.app_with_bundle_id(FORGE_BUNDLE_ID, APPLE_BUNDLE_ID)
end
def version_from_mdls(path)
version = Utils.popen_read(
"/usr/bin/mdls", "-raw", "-nullMarker", "", "-name", "kMDItemVersion", path.to_s
).strip
version unless version.empty?
end
# The XQuartz that Apple shipped in OS X through 10.7 does not have a
# pkg-util entry, so if Spotlight indexing is disabled we must make an
# educated guess as to what version is installed.
def guess_system_version
case MacOS.version
when '10.5' then '2.1.6'
when '10.6' then '2.3.6'
when '10.7' then '2.6.3'
else 'dunno'
end
end
# Upstream XQuartz *does* have a pkg-info entry, so if we can't get it
# from mdls, we can try pkgutil. This is very slow.
def version_from_pkgutil
str = MacOS.pkgutil_info(FORGE_PKG_ID)[/version: (\d\.\d\.\d+)$/, 1]
PKGINFO_VERSION_MAP.fetch(str, str)
end
def provided_by_apple?
[FORGE_BUNDLE_ID, APPLE_BUNDLE_ID].find do |id|
MacOS.app_with_bundle_id(id)
end == APPLE_BUNDLE_ID
end
# This should really be private, but for compatibility reasons it must
# remain public. New code should use MacOS::X11.{bin,lib,include}
# instead, as that accounts for Xcode-only systems.
def prefix
@prefix ||= if Pathname.new('/opt/X11/lib/libpng.dylib').exist?
Pathname.new('/opt/X11')
elsif Pathname.new('/usr/X11/lib/libpng.dylib').exist?
Pathname.new('/usr/X11')
end
end
def installed?
!version.nil? && !prefix.nil?
end
# If XQuartz and/or the CLT are installed, headers will be found under
# /opt/X11/include or /usr/X11/include. For Xcode-only systems, they are
# found in the SDK, so we use sdk_path for both the headers and libraries.
# Confusingly, executables (e.g. config scripts) are only found under
# /opt/X11/bin or /usr/X11/bin in all cases.
def effective_prefix
if provided_by_apple? && Xcode.without_clt?
Pathname.new("#{OS::Mac.sdk_path}/usr/X11")
else
prefix
end
end
def bin
prefix/"bin"
end
def include
effective_prefix/"include"
end
def lib
effective_prefix/"lib"
end
def share
prefix/"share"
end
end
end
end

View File

@ -0,0 +1,183 @@
require 'resource'
require 'erb'
module Patch
def self.create(strip, src, &block)
case strip
when :DATA
DATAPatch.new(:p1)
when String
StringPatch.new(:p1, strip)
when Symbol
case src
when :DATA
DATAPatch.new(strip)
when String
StringPatch.new(strip, src)
else
ExternalPatch.new(strip, &block)
end
else
raise ArgumentError, "unexpected value #{strip.inspect} for strip"
end
end
def self.normalize_legacy_patches(list)
patches = []
case list
when Hash
list
when Array, String, :DATA
{ :p1 => list }
else
{}
end.each_pair do |strip, urls|
Array(urls).each do |url|
case url
when :DATA
patch = DATAPatch.new(strip)
else
patch = LegacyPatch.new(strip, url)
end
patches << patch
end
end
patches
end
end
class EmbeddedPatch
attr_writer :owner
attr_reader :strip
def initialize(strip)
@strip = strip
end
def external?
false
end
def contents
raise NotImplementedError
end
def apply
data = contents.gsub("HOMEBREW_PREFIX", HOMEBREW_PREFIX)
cmd, args = "/usr/bin/patch", %W[-g 0 -f -#{strip}]
IO.popen("#{cmd} #{args.join(" ")}", "w") { |p| p.write(data) }
raise ErrorDuringExecution.new(cmd, args) unless $?.success?
end
def inspect
"#<#{self.class.name}: #{strip.inspect}>"
end
end
class DATAPatch < EmbeddedPatch
attr_accessor :path
def initialize(strip)
super
@path = nil
end
def contents
data = ""
path.open("rb") do |f|
begin
line = f.gets
end until line.nil? || /^__END__$/ === line
data << line while line = f.gets
end
data
end
end
class StringPatch < EmbeddedPatch
def initialize(strip, str)
super(strip)
@str = str
end
def contents
@str
end
end
class ExternalPatch
attr_reader :resource, :strip
def initialize(strip, &block)
@strip = strip
@resource = Resource.new("patch", &block)
end
def external?
true
end
def owner= owner
resource.owner = owner
resource.version = resource.checksum || ERB::Util.url_encode(resource.url)
end
def apply
dir = Pathname.pwd
resource.unpack do
# Assumption: the only file in the staging directory is the patch
patchfile = Pathname.pwd.children.first
safe_system "/usr/bin/patch", "-g", "0", "-f", "-d", dir, "-#{strip}", "-i", patchfile
end
end
def url
resource.url
end
def fetch
resource.fetch
end
def verify_download_integrity(fn)
resource.verify_download_integrity(fn)
end
def cached_download
resource.cached_download
end
def clear_cache
resource.clear_cache
end
def inspect
"#<#{self.class.name}: #{strip.inspect} #{url.inspect}>"
end
end
# Legacy patches have no checksum and are not cached
class LegacyPatch < ExternalPatch
def initialize(strip, url)
super(strip)
resource.url = url
resource.download_strategy = CurlDownloadStrategy
end
def fetch
clear_cache
super
end
def verify_download_integrity(fn)
# no-op
end
def apply
super
ensure
clear_cache
end
end

View File

@ -0,0 +1,36 @@
require 'version'
class PkgVersion < Version
attr_reader :version, :revision
RX = /\A(.+?)(?:_(\d+))?\z/
def self.parse(path)
_, version, revision = *path.match(RX)
new(version, revision)
end
def initialize(version, revision)
super(version)
if head?
@revision = 0
else
@revision = revision.to_i
end
end
def to_s
if revision > 0
"#{version}_#{revision}"
else
version
end
end
alias_method :to_str, :to_s
def <=>(other)
return unless Version === other
super.nonzero? || revision <=> other.revision
end
end

View File

@ -0,0 +1,196 @@
require 'dependable'
require 'dependency'
require 'dependencies'
require 'build_environment'
# :startdoc:
# A base class for non-formula requirements needed by formulae.
# A "fatal" requirement is one that will fail the build if it is not present.
# By default, Requirements are non-fatal.
class Requirement
include Dependable
attr_reader :tags, :name, :option_name
def initialize(tags=[])
@tags = tags
@tags << :build if self.class.build
@name ||= infer_name
@option_name = @name
end
# The message to show when the requirement is not met.
def message; "" end
# Overriding #satisfied? is deprecated.
# Pass a block or boolean to the satisfy DSL method instead.
def satisfied?
result = self.class.satisfy.yielder { |p| instance_eval(&p) }
@satisfied_result = result
!!result
end
# Can overridden to optionally prevent a formula with this requirement from
# pouring a bottle.
def pour_bottle?; true end
# Overriding #fatal? is deprecated.
# Pass a boolean to the fatal DSL method instead.
def fatal?
self.class.fatal || false
end
def default_formula?
self.class.default_formula || false
end
# Overriding #modify_build_environment is deprecated.
# Pass a block to the the env DSL method instead.
# Note: #satisfied? should be called before invoking this method
# as the env modifications may depend on its side effects.
def modify_build_environment
instance_eval(&env_proc) if env_proc
# XXX If the satisfy block returns a Pathname, then make sure that it
# remains available on the PATH. This makes requirements like
# satisfy { which("executable") }
# work, even under superenv where "executable" wouldn't normally be on the
# PATH.
# This is undocumented magic and it should be removed, but we need to add
# a way to declare path-based requirements that work with superenv first.
if Pathname === @satisfied_result
parent = @satisfied_result.parent
unless ENV["PATH"].split(File::PATH_SEPARATOR).include?(parent.to_s)
ENV.append_path("PATH", parent)
end
end
end
def env
self.class.env
end
def env_proc
self.class.env_proc
end
def eql?(other)
instance_of?(other.class) && name == other.name && tags == other.tags
end
def hash
[name, *tags].hash
end
def inspect
"#<#{self.class.name}: #{name.inspect} #{tags.inspect}>"
end
def to_dependency
f = self.class.default_formula
raise "No default formula defined for #{inspect}" if f.nil?
Dependency.new(f, tags, method(:modify_build_environment), name)
end
private
def infer_name
klass = self.class.name || self.class.to_s
klass.sub!(/(Dependency|Requirement)$/, '')
klass.sub!(/^(\w+::)*/, '')
klass.downcase
end
def which(cmd)
super(cmd, ORIGINAL_PATHS.join(File::PATH_SEPARATOR))
end
# :stopdoc:
class << self
include BuildEnvironmentDSL
attr_reader :env_proc
attr_rw :fatal, :default_formula
# build is deprecated, use `depends_on <requirement> => :build` instead
attr_rw :build
def satisfy(options={}, &block)
@satisfied ||= Requirement::Satisfier.new(options, &block)
end
def env(*settings, &block)
if block_given?
@env_proc = block
else
super
end
end
end
class Satisfier
def initialize(options, &block)
case options
when Hash
@options = { :build_env => true }
@options.merge!(options)
else
@satisfied = options
end
@proc = block
end
def yielder
if instance_variable_defined?(:@satisfied)
@satisfied
elsif @options[:build_env]
require "extend/ENV"
ENV.with_build_environment { yield @proc }
else
yield @proc
end
end
end
class << self
# Expand the requirements of dependent recursively, optionally yielding
# [dependent, req] pairs to allow callers to apply arbitrary filters to
# the list.
# The default filter, which is applied when a block is not given, omits
# optionals and recommendeds based on what the dependent has asked for.
def expand(dependent, &block)
reqs = Requirements.new
formulae = dependent.recursive_dependencies.map(&:to_formula)
formulae.unshift(dependent)
formulae.each do |f|
f.requirements.each do |req|
if prune?(f, req, &block)
next
else
reqs << req
end
end
end
reqs
end
def prune?(dependent, req, &block)
catch(:prune) do
if block_given?
yield dependent, req
elsif req.optional? || req.recommended?
prune unless dependent.build.with?(req)
end
end
end
# Used to prune requirements when calling expand with a block.
def prune
throw(:prune, true)
end
end
end

View File

@ -0,0 +1,130 @@
require 'requirement'
require 'requirements/fortran_dependency'
require 'requirements/language_module_dependency'
require 'requirements/minimum_macos_requirement'
require 'requirements/mpi_dependency'
require 'requirements/osxfuse_dependency'
require 'requirements/python_dependency'
require 'requirements/x11_dependency'
class XcodeDependency < Requirement
fatal true
satisfy(:build_env => false) { MacOS::Xcode.installed? }
def message
message = <<-EOS.undent
A full installation of Xcode.app is required to compile this software.
Installing just the Command Line Tools is not sufficient.
EOS
if MacOS.version >= :lion
message += <<-EOS.undent
Xcode can be installed from the App Store.
EOS
else
message += <<-EOS.undent
Xcode can be installed from https://developer.apple.com/downloads/
EOS
end
end
end
class MysqlDependency < Requirement
fatal true
default_formula 'mysql'
satisfy { which 'mysql_config' }
end
class PostgresqlDependency < Requirement
fatal true
default_formula 'postgresql'
satisfy { which 'pg_config' }
end
class TeXDependency < Requirement
fatal true
satisfy { which('tex') || which('latex') }
def message;
if File.exist?("/usr/texbin")
texbin_path = "/usr/texbin"
else
texbin_path = "its bin directory"
end
<<-EOS.undent
A LaTeX distribution is required for Homebrew to install this formula.
You can install MacTeX distribution from:
http://www.tug.org/mactex/
Make sure that "/usr/texbin", or the location you installed it to, is in
your PATH before proceeding.
EOS
end
end
class ArchRequirement < Requirement
fatal true
def initialize(arch)
@arch = arch.pop
super
end
satisfy do
case @arch
when :x86_64 then MacOS.prefer_64_bit?
when :intel, :ppc then Hardware::CPU.type == @arch
end
end
def message
"This formula requires an #{@arch} architecture."
end
end
class MercurialDependency < Requirement
fatal true
default_formula 'mercurial'
satisfy { which('hg') }
end
class GitDependency < Requirement
fatal true
default_formula 'git'
satisfy { !!which('git') }
end
class JavaDependency < Requirement
fatal true
satisfy { java_version }
def initialize(tags)
@version = tags.pop
super
end
def java_version
args = %w[/usr/libexec/java_home --failfast]
args << "--version" << "#{@version}+" if @version
quiet_system(*args)
end
def message
version_string = " #{@version}" if @version
<<-EOS.undent
Java#{version_string} is required to install this formula.
You can install Java from:
http://www.oracle.com/technetwork/java/javase/downloads/index.html
Make sure you install both the JRE and JDK.
EOS
end
end

View File

@ -0,0 +1,13 @@
require 'requirement'
class FortranDependency < Requirement
fatal true
default_formula 'gcc'
env { ENV.fortran }
satisfy :build_env => false do
(ENV['FC'] || which('gfortran')) ? true : false
end
end

View File

@ -0,0 +1,51 @@
require 'requirement'
class LanguageModuleDependency < Requirement
fatal true
def initialize language, module_name, import_name=nil
@language = language
@module_name = module_name
@import_name = import_name || module_name
super([language, module_name, import_name])
end
satisfy(:build_env => false) { quiet_system(*the_test) }
def message; <<-EOS.undent
Unsatisfied dependency: #{@module_name}
Homebrew does not provide #{@language.to_s.capitalize} dependencies; install with:
#{command_line} #{@module_name}
EOS
end
def the_test
case @language
when :chicken then %W{/usr/bin/env csi -e (use\ #{@import_name})}
when :jruby then %W{/usr/bin/env jruby -rubygems -e require\ '#{@import_name}'}
when :lua then %W{/usr/bin/env luarocks show #{@import_name}}
when :node then %W{/usr/bin/env node -e require('#{@import_name}');}
when :ocaml then %W{/usr/bin/env opam list #{@import_name}}
when :perl then %W{/usr/bin/env perl -e use\ #{@import_name}}
when :python then %W{/usr/bin/env python -c import\ #{@import_name}}
when :python3 then %W{/usr/bin/env python3 -c import\ #{@import_name}}
when :ruby then %W{/usr/bin/env ruby -rubygems -e require\ '#{@import_name}'}
when :rbx then %W{/usr/bin/env rbx -rubygems -e require\ '#{@import_name}'}
end
end
def command_line
case @language
when :chicken then "chicken-install"
when :jruby then "jruby -S gem install"
when :lua then "luarocks install"
when :node then "npm install"
when :ocaml then "opam install"
when :perl then "cpan -i"
when :python then "pip install"
when :python3 then "pip3 install"
when :rbx then "rbx gem install"
when :ruby then "gem install"
end
end
end

View File

@ -0,0 +1,11 @@
require 'dependency'
# This special dependency ensures that the Tigerbrew ld64
# formula is used as gcc's ld in place of the old version
# that comes with the OS.
class LD64Dependency < Dependency
def initialize(name='ld64', tags=[:build], env_proc=nil)
super
@env_proc = proc { ENV.ld64 }
end
end

View File

@ -0,0 +1,16 @@
require 'requirement'
class MinimumMacOSRequirement < Requirement
fatal true
def initialize(tags)
@version = MacOS::Version.from_symbol(tags.first)
super
end
satisfy { MacOS.version >= @version }
def message
"OS X #{@version.pretty_name} or newer is required."
end
end

View File

@ -0,0 +1,63 @@
require 'requirement'
# There are multiple implementations of MPI-2 available.
# http://www.mpi-forum.org/
# This requirement is used to find an appropriate one.
class MPIDependency < Requirement
attr_reader :lang_list
fatal true
default_formula 'open-mpi'
env :userpaths
# This method must accept varargs rather than an array for
# backwards compatibility with formulae that call it directly.
def initialize(*tags)
@non_functional = []
@unknown_langs = []
@lang_list = [:cc, :cxx, :f77, :f90] & tags
tags -= @lang_list
super(tags)
end
def mpi_wrapper_works? compiler
compiler = which compiler
return false if compiler.nil? or not compiler.executable?
# Some wrappers are non-functional and will return a non-zero exit code
# when invoked for version info.
#
# NOTE: A better test may be to do a small test compilation a la autotools.
quiet_system compiler, '--version'
end
satisfy do
@lang_list.each do |lang|
case lang
when :cc, :cxx, :f90, :f77
compiler = 'mpi' + lang.to_s
@non_functional << compiler unless mpi_wrapper_works? compiler
else
@unknown_langs << lang.to_s
end
end
@unknown_langs.empty? and @non_functional.empty?
end
env do
# Set environment variables to help configure scripts find MPI compilers.
# Variable names taken from:
# http://www.gnu.org/software/autoconf-archive/ax_mpi.html
@lang_list.each do |lang|
compiler = 'mpi' + lang.to_s
mpi_path = which compiler
# Fortran 90 environment var has a different name
compiler = 'MPIFC' if lang == :f90
ENV[compiler.upcase] = mpi_path
end
end
end

View File

@ -0,0 +1,28 @@
require "requirement"
class OsxfuseDependency < Requirement
fatal true
default_formula "osxfuse"
satisfy { Formula["osxfuse"].installed? || self.class.binary_osxfuse_installed? }
def self.binary_osxfuse_installed?
File.exist?("/usr/local/include/osxfuse/fuse.h") && !File.symlink?("/usr/local/include/osxfuse")
end
env do
ENV.append_path "PKG_CONFIG_PATH", HOMEBREW_PREFIX/"Library/ENV/pkgconfig/fuse"
end
end
class ConflictsWithBinaryOsxfuse < Requirement
fatal true
satisfy { HOMEBREW_PREFIX.to_s != "/usr/local" || !OsxfuseDependency.binary_osxfuse_installed? }
def message
<<-EOS.undent
osxfuse is already installed from the binary distribution and
conflicts with this formula.
EOS
end
end

View File

@ -0,0 +1,58 @@
require "language/python"
class PythonDependency < Requirement
fatal true
default_formula "python"
satisfy :build_env => false do
python = which_python
next unless python
version = python_short_version
next unless version
# Always use Python 2.7 for consistency on older versions of OSX.
version == Version.new("2.7")
end
def pour_bottle?
build? || system_python?
end
def modify_build_environment
if system_python?
if python_binary == "python"
version = python_short_version
ENV["PYTHONPATH"] = "#{HOMEBREW_PREFIX}/lib/python#{version}/site-packages"
end
elsif which_python
ENV.prepend_path "PATH", which_python.dirname
end
end
def python_short_version
@short_version ||= Language::Python.major_minor_version which_python
end
def which_python
python = which python_binary
return unless python
executable = `#{python} -c "import sys; print(sys.executable)"`.strip
return unless executable
Pathname.new executable
end
def system_python; "/usr/bin/#{python_binary}" end
def system_python?; system_python == which_python.to_s end
def python_binary; "python" end
# Deprecated
alias_method :to_s, :python_binary
end
class Python3Dependency < PythonDependency
fatal true
default_formula "python3"
satisfy(:build_env => false) { which_python }
def python_binary; "python3" end
end

View File

@ -0,0 +1,44 @@
require "requirement"
class X11Dependency < Requirement
include Comparable
attr_reader :min_version
fatal true
env { ENV.x11 }
def initialize(name="x11", tags=[])
@name = name
if /(\d\.)+\d/ === tags.first
@min_version = Version.new(tags.shift)
else
@min_version = Version.new("0.0.0")
end
super(tags)
end
satisfy :build_env => false do
MacOS::XQuartz.installed? && min_version <= Version.new(MacOS::XQuartz.version)
end
def message; <<-EOS.undent
Unsatisfied dependency: XQuartz #{@min_version}
Homebrew does not package XQuartz. Installers may be found at:
https://xquartz.macosforge.org
EOS
end
def <=> other
return unless X11Dependency === other
min_version <=> other.min_version
end
def eql?(other)
super && min_version == other.min_version
end
def inspect
"#<#{self.class.name}: #{name.inspect} #{tags.inspect} min_version=#{min_version}>"
end
end

View File

@ -0,0 +1,139 @@
require 'download_strategy'
require 'checksum'
require 'version'
# Resource is the fundamental representation of an external resource. The
# primary formula download, along with other declared resources, are instances
# of this class.
class Resource
include FileUtils
attr_reader :checksum, :mirrors, :specs, :using
attr_writer :url, :checksum, :version
attr_accessor :download_strategy
# Formula name must be set after the DSL, as we have no access to the
# formula name before initialization of the formula
attr_accessor :name, :owner
def initialize name=nil, &block
@name = name
@url = nil
@version = nil
@mirrors = []
@specs = {}
@checksum = nil
@using = nil
instance_eval(&block) if block_given?
end
def downloader
@downloader ||= download_strategy.new(download_name, self)
end
# Removes /s from resource names; this allows go package names
# to be used as resource names without confusing software that
# interacts with download_name, e.g. github.com/foo/bar
def escaped_name
name.gsub("/", '-')
end
def download_name
name.nil? ? owner.name : "#{owner.name}--#{escaped_name}"
end
def cached_download
downloader.cached_location
end
def clear_cache
downloader.clear_cache
end
# Fetch, verify, and unpack the resource
def stage(target=nil, &block)
verify_download_integrity(fetch)
unpack(target, &block)
end
# If a target is given, unpack there; else unpack to a temp folder
# If block is given, yield to that block
# A target or a block must be given, but not both
def unpack(target=nil)
mktemp(download_name) do
downloader.stage
if block_given?
yield self
elsif target
target = Pathname.new(target) unless target.is_a? Pathname
target.install Dir['*']
end
end
end
Partial = Struct.new(:resource, :files)
def files(*files)
Partial.new(self, files)
end
# For brew-fetch and others.
def fetch
# Ensure the cache exists
HOMEBREW_CACHE.mkpath
downloader.fetch
rescue ErrorDuringExecution, CurlDownloadStrategyError => e
raise DownloadError.new(self, e)
else
cached_download
end
def verify_download_integrity fn
if fn.respond_to?(:file?) && fn.file?
ohai "Verifying #{fn.basename} checksum" if ARGV.verbose?
fn.verify_checksum(checksum)
end
rescue ChecksumMissingError
opoo "Cannot verify integrity of #{fn.basename}"
puts "A checksum was not provided for this resource"
puts "For your reference the SHA1 is: #{fn.sha1}"
end
Checksum::TYPES.each do |type|
define_method(type) { |val| @checksum = Checksum.new(type, val) }
end
def url val=nil, specs={}
return @url if val.nil?
@url = val
@specs.merge!(specs)
@using = @specs.delete(:using)
@download_strategy = DownloadStrategyDetector.detect(url, using)
end
def version val=nil
@version ||= detect_version(val)
end
def mirror val
mirrors << val
end
private
def detect_version(val)
case val
when nil then Version.detect(url, specs)
when String then Version.new(val)
when Version then val
else
raise TypeError, "version '#{val.inspect}' should be a string"
end
end
class Go < Resource
def stage target
super(target/name)
end
end
end

View File

@ -0,0 +1,271 @@
require 'forwardable'
require 'resource'
require 'checksum'
require 'version'
require 'options'
require 'build_options'
require 'dependency_collector'
require 'bottles'
require 'patch'
require 'compilers'
class SoftwareSpec
extend Forwardable
PREDEFINED_OPTIONS = {
:universal => Option.new("universal", "Build a universal binary"),
:cxx11 => Option.new("c++11", "Build using C++11 mode"),
"32-bit" => Option.new("32-bit", "Build 32-bit only"),
}
attr_reader :name, :owner
attr_reader :build, :resources, :patches, :options
attr_reader :dependency_collector
attr_reader :bottle_specification
attr_reader :compiler_failures
def_delegators :@resource, :stage, :fetch, :verify_download_integrity
def_delegators :@resource, :cached_download, :clear_cache
def_delegators :@resource, :checksum, :mirrors, :specs, :using
def_delegators :@resource, :version, :mirror, *Checksum::TYPES
def initialize
@resource = Resource.new
@resources = {}
@dependency_collector = DependencyCollector.new
@bottle_specification = BottleSpecification.new
@patches = []
@options = Options.new
@build = BuildOptions.new(Options.create(ARGV.flags_only), options)
@compiler_failures = []
end
def owner= owner
@name = owner.name
@owner = owner
@resource.owner = self
resources.each_value do |r|
r.owner = self
r.version ||= version
end
patches.each { |p| p.owner = self }
end
def url val=nil, specs={}
return @resource.url if val.nil?
@resource.url(val, specs)
dependency_collector.add(@resource)
end
def bottled?
bottle_specification.tag?(bottle_tag)
end
def bottle &block
bottle_specification.instance_eval(&block)
end
def resource_defined? name
resources.has_key?(name)
end
def resource name, klass=Resource, &block
if block_given?
raise DuplicateResourceError.new(name) if resource_defined?(name)
res = klass.new(name, &block)
resources[name] = res
dependency_collector.add(res)
else
resources.fetch(name) { raise ResourceMissingError.new(owner, name) }
end
end
def option_defined?(name)
options.include?(name)
end
def option(name, description="")
opt = PREDEFINED_OPTIONS.fetch(name) do
if Symbol === name
opoo "Passing arbitrary symbols to `option` is deprecated: #{name.inspect}"
puts "Symbols are reserved for future use, please pass a string instead"
name = name.to_s
end
raise ArgumentError, "option name is required" if name.empty?
raise ArgumentError, "option name must be longer than one character" unless name.length > 1
raise ArgumentError, "option name must not start with dashes" if name.start_with?("-")
Option.new(name, description)
end
options << opt
end
def depends_on spec
dep = dependency_collector.add(spec)
add_dep_option(dep) if dep
end
def deps
dependency_collector.deps
end
def requirements
dependency_collector.requirements
end
def patch strip=:p1, src=nil, &block
patches << Patch.create(strip, src, &block)
end
def fails_with compiler, &block
compiler_failures << CompilerFailure.create(compiler, &block)
end
def needs *standards
standards.each do |standard|
compiler_failures.concat CompilerFailure.for_standard(standard)
end
end
def add_legacy_patches(list)
list = Patch.normalize_legacy_patches(list)
list.each { |p| p.owner = self }
patches.concat(list)
end
def add_dep_option(dep)
name = dep.option_name
if dep.optional? && !option_defined?("with-#{name}")
options << Option.new("with-#{name}", "Build with #{name} support")
elsif dep.recommended? && !option_defined?("without-#{name}")
options << Option.new("without-#{name}", "Build without #{name} support")
end
end
end
class HeadSoftwareSpec < SoftwareSpec
def initialize
super
@resource.version = Version.new('HEAD')
end
def verify_download_integrity fn
return
end
end
class Bottle
class Filename
attr_reader :name, :version, :tag, :revision
def self.create(formula, tag, revision)
new(formula.name, formula.pkg_version, tag, revision)
end
def initialize(name, version, tag, revision)
@name = name
@version = version
@tag = tag
@revision = revision
end
def to_s
prefix + suffix
end
alias_method :to_str, :to_s
def prefix
"#{name}-#{version}.#{tag}"
end
def suffix
s = revision > 0 ? ".#{revision}" : ""
".bottle#{s}.tar.gz"
end
end
extend Forwardable
attr_reader :name, :resource, :prefix, :cellar, :revision
def_delegators :resource, :url, :fetch, :verify_download_integrity
def_delegators :resource, :cached_download, :clear_cache
def initialize(formula, spec)
@name = formula.name
@resource = Resource.new
@resource.owner = formula
checksum, tag = spec.checksum_for(bottle_tag)
filename = Filename.create(formula, tag, spec.revision)
@resource.url = build_url(spec.root_url, filename)
@resource.download_strategy = CurlBottleDownloadStrategy
@resource.version = formula.pkg_version
@resource.checksum = checksum
@prefix = spec.prefix
@cellar = spec.cellar
@revision = spec.revision
end
def compatible_cellar?
cellar == :any || cellar == HOMEBREW_CELLAR.to_s
end
def stage
resource.downloader.stage
end
private
def build_url(root_url, filename)
"#{root_url}/#{filename}"
end
end
class BottleSpecification
DEFAULT_PREFIX = "/usr/local".freeze
DEFAULT_CELLAR = "/usr/local/Cellar".freeze
DEFAULT_ROOT_URL = "https://downloads.sf.net/project/machomebrew/Bottles".freeze
attr_rw :root_url, :prefix, :cellar, :revision
attr_reader :checksum, :collector
def initialize
@revision = 0
@prefix = DEFAULT_PREFIX
@cellar = DEFAULT_CELLAR
@root_url = DEFAULT_ROOT_URL
@collector = BottleCollector.new
end
def tag?(tag)
!!checksum_for(tag)
end
# Checksum methods in the DSL's bottle block optionally take
# a Hash, which indicates the platform the checksum applies on.
Checksum::TYPES.each do |cksum|
define_method(cksum) do |val|
digest, tag = val.shift
collector[tag] = Checksum.new(cksum, digest)
end
end
def checksum_for(tag)
collector.fetch_checksum_for(tag)
end
def checksums
checksums = {}
os_versions = collector.keys
os_versions.map! {|osx| MacOS::Version.from_symbol osx rescue nil }.compact!
os_versions.sort.reverse_each do |os_version|
osx = os_version.to_sym
checksum = collector[osx]
checksums[checksum.hash_type] ||= []
checksums[checksum.hash_type] << { checksum => osx }
end
checksums
end
end

View File

@ -0,0 +1,153 @@
require 'cxxstdlib'
require 'ostruct'
require 'options'
require 'utils/json'
# Inherit from OpenStruct to gain a generic initialization method that takes a
# hash and creates an attribute for each key and value. `Tab.new` probably
# should not be called directly, instead use one of the class methods like
# `Tab.create`.
class Tab < OpenStruct
FILENAME = 'INSTALL_RECEIPT.json'
def self.create(formula, compiler, stdlib, build)
Tab.new :used_options => build.used_options.as_flags,
:unused_options => build.unused_options.as_flags,
:tabfile => formula.prefix.join(FILENAME),
:built_as_bottle => !!ARGV.build_bottle?,
:poured_from_bottle => false,
:tapped_from => formula.tap,
:time => Time.now.to_i,
:HEAD => Homebrew.git_head,
:compiler => compiler,
:stdlib => stdlib
end
def self.from_file path
attributes = Utils::JSON.load(File.read(path))
attributes[:tabfile] = path
new(attributes)
end
def self.for_keg keg
path = keg.join(FILENAME)
if path.exist?
from_file(path)
else
dummy_tab
end
end
def self.for_name name
for_formula(Formulary.factory(name))
end
def self.for_formula f
paths = []
if f.opt_prefix.symlink? && f.opt_prefix.directory?
paths << f.opt_prefix.resolved_path
end
if f.linked_keg.symlink? && f.linked_keg.directory?
paths << f.linked_keg.resolved_path
end
if f.rack.directory? && (dirs = f.rack.subdirs).length == 1
paths << dirs.first
end
paths << f.prefix
path = paths.map { |pn| pn.join(FILENAME) }.find(&:file?)
if path
from_file(path)
else
dummy_tab(f)
end
end
def self.dummy_tab f=nil
Tab.new :used_options => [],
:unused_options => (f.options.as_flags rescue []),
:built_as_bottle => false,
:poured_from_bottle => false,
:tapped_from => "",
:time => nil,
:HEAD => nil,
:stdlib => nil,
:compiler => :clang
end
def with? name
include?("with-#{name}") || unused_options.include?("without-#{name}")
end
def without? name
not with? name
end
def include? opt
used_options.include? opt
end
def universal?
include?("universal")
end
def cxx11?
include?("c++11")
end
def build_32_bit?
include?("32-bit")
end
def used_options
Options.create(super)
end
def unused_options
Options.create(super)
end
def cxxstdlib
# Older tabs won't have these values, so provide sensible defaults
lib = stdlib.to_sym if stdlib
cc = compiler || MacOS.default_compiler
CxxStdlib.create(lib, cc.to_sym)
end
def to_json
Utils::JSON.dump({
:used_options => used_options.as_flags,
:unused_options => unused_options.as_flags,
:built_as_bottle => built_as_bottle,
:poured_from_bottle => poured_from_bottle,
:tapped_from => tapped_from,
:time => time,
:HEAD => self.HEAD,
:stdlib => (stdlib.to_s if stdlib),
:compiler => (compiler.to_s if compiler)})
end
def write
tabfile.atomic_write(to_json)
end
def to_s
s = []
case poured_from_bottle
when true then s << "Poured from bottle"
when false then s << "Built from source"
end
unless used_options.empty?
s << "Installed" if s.empty?
s << "with:"
s << used_options.to_a.join(", ")
end
s.join(" ")
end
end

View File

@ -0,0 +1,8 @@
# match expressions when taps are given as ARGS, e.g. someuser/sometap
HOMEBREW_TAP_ARGS_REGEX = %r{^([\w-]+)/(homebrew-)?([\w-]+)$}
# match taps' formula, e.g. someuser/sometap/someformula
HOMEBREW_TAP_FORMULA_REGEX = %r{^([\w-]+)/([\w-]+)/([\w+-.]+)$}
# match taps' directory path, e.g. HOMEBREW_LIBRARY/Taps/someuser/sometap
HOMEBREW_TAP_DIR_REGEX = %r{#{Regexp.escape(HOMEBREW_LIBRARY.to_s)}/Taps/([\w-]+)/([\w-]+)}
# match taps' formula path, e.g. HOMEBREW_LIBRARY/Taps/someuser/sometap/someformula
HOMEBREW_TAP_PATH_REGEX = Regexp.new(HOMEBREW_TAP_DIR_REGEX.source + %r{/(.*)}.source)

View File

@ -0,0 +1,70 @@
TAP_MIGRATIONS = {
"agedu" => "homebrew/headonly",
"aimage" => "homebrew/boneyard",
"aplus" => "homebrew/boneyard",
"apple-gcc42" => "homebrew/dupes",
"appledoc" => "homebrew/boneyard",
"appswitch" => "homebrew/binary",
"aws-iam-tools" => "homebrew/boneyard",
"blackbox" => "homebrew/boneyard",
"boost149" => "homebrew/versions",
"cantera" => "homebrew/science",
"catdoc" => "homebrew/boneyard",
"clam" => "homebrew/boneyard",
"cloudfoundry-cli" => "pivotal/tap",
"cmucl" => "homebrew/binary",
"comparepdf" => "homebrew/boneyard",
"connect" => "homebrew/boneyard",
"denyhosts" => "homebrew/boneyard",
"dotwrp" => "homebrew/science",
"drizzle" => "homebrew/boneyard",
"drush" => "homebrew/php",
"dsniff" => "homebrew/boneyard",
"electric-fence" => "homebrew/boneyard",
"gnunet" => "homebrew/boneyard",
"grads" => "homebrew/binary",
"gromacs" => "homebrew/science",
"hllib" => "homebrew/boneyard",
"hugs98" => "homebrew/boneyard",
"hwloc" => "homebrew/science",
"ipopt" => "homebrew/science",
"iulib" => "homebrew/boneyard",
"jscoverage" => "homebrew/boneyard",
"jsl" => "homebrew/binary",
"jstalk" => "homebrew/boneyard",
"justniffer" => "homebrew/boneyard",
"kerl" => "homebrew/headonly",
"kismet" => "homebrew/boneyard",
"libdlna" => "homebrew/boneyard",
"libgtextutils" => "homebrew/science",
"librets" => "homebrew/boneyard",
"libspotify" => "homebrew/binary",
"lmutil" => "homebrew/binary",
"metalua" => "homebrew/boneyard",
"mlkit" => "homebrew/boneyard",
"mlton" => "homebrew/boneyard",
"mpio" => "homebrew/boneyard",
"msgpack-rpc" => "homebrew/boneyard",
"mydumper" => "homebrew/boneyard",
"nlopt" => "homebrew/science",
"octave" => "homebrew/science",
"opencv" => "homebrew/science",
"openfst" => "homebrew/science",
"opengrm-ngram" => "homebrew/science",
"pan" => "homebrew/boneyard",
"pjsip" => "homebrew/boneyard",
"pocl" => "homebrew/science",
"qfits" => "homebrew/boneyard",
"qrupdate" => "homebrew/science",
"shark" => "homebrew/boneyard",
"slicot" => "homebrew/science",
"solfege" => "homebrew/boneyard",
"sundials" => "homebrew/science",
"syslog-ng" => "homebrew/boneyard",
"tetgen" => "homebrew/science",
"texmacs" => "homebrew/boneyard",
"tmap" => "homebrew/boneyard",
"urweb" => "homebrew/boneyard",
"ushare" => "homebrew/boneyard",
"wkhtmltopdf" => "homebrew/boneyard",
}

View File

@ -0,0 +1,159 @@
# Require this file to build a testing environment.
$:.push(File.expand_path(__FILE__+'/../..'))
require 'extend/module'
require 'extend/fileutils'
require 'extend/pathname'
require 'extend/ARGV'
require 'extend/string'
require 'extend/symbol'
require 'extend/enumerable'
require 'exceptions'
require 'utils'
require 'rbconfig'
require 'tmpdir'
TEST_TMPDIR = Dir.mktmpdir("homebrew_tests")
at_exit { FileUtils.remove_entry(TEST_TMPDIR) }
# Constants normally defined in global.rb
HOMEBREW_PREFIX = Pathname.new(TEST_TMPDIR).join("prefix")
HOMEBREW_REPOSITORY = HOMEBREW_PREFIX
HOMEBREW_LIBRARY = HOMEBREW_REPOSITORY+'Library'
HOMEBREW_CACHE = HOMEBREW_PREFIX.parent+'cache'
HOMEBREW_CACHE_FORMULA = HOMEBREW_PREFIX.parent+'formula_cache'
HOMEBREW_CELLAR = HOMEBREW_PREFIX.parent+'cellar'
HOMEBREW_LOGS = HOMEBREW_PREFIX.parent+'logs'
HOMEBREW_TEMP = Pathname.new(ENV.fetch('HOMEBREW_TEMP', '/tmp'))
HOMEBREW_USER_AGENT = 'Homebrew'
HOMEBREW_WWW = 'http://example.com'
HOMEBREW_CURL_ARGS = '-fsLA'
HOMEBREW_VERSION = '0.9-test'
require 'tap_constants'
if RbConfig.respond_to?(:ruby)
RUBY_PATH = Pathname.new(RbConfig.ruby)
else
RUBY_PATH = Pathname.new(RbConfig::CONFIG["bindir"]).join(
RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]
)
end
RUBY_BIN = RUBY_PATH.dirname
MACOS_FULL_VERSION = `/usr/bin/sw_vers -productVersion`.chomp
MACOS_VERSION = ENV.fetch('MACOS_VERSION') { MACOS_FULL_VERSION[/10\.\d+/] }
ORIGINAL_PATHS = ENV['PATH'].split(File::PATH_SEPARATOR).map{ |p| Pathname.new(p).expand_path rescue nil }.compact.freeze
# Test environment setup
%w{ENV Formula}.each { |d| HOMEBREW_LIBRARY.join(d).mkpath }
%w{cache formula_cache cellar logs}.each { |d| HOMEBREW_PREFIX.parent.join(d).mkpath }
# Test fixtures and files can be found relative to this path
TEST_DIRECTORY = File.dirname(File.expand_path(__FILE__))
ARGV.extend(HomebrewArgvExtension)
begin
require "rubygems"
require "minitest/autorun"
require "mocha/setup"
rescue LoadError
abort "Run `rake deps` or install the mocha and minitest gems before running the tests"
end
module Homebrew
include FileUtils
extend self
module VersionAssertions
def version v
Version.new(v)
end
def assert_version_equal expected, actual
assert_equal Version.new(expected), actual
end
def assert_version_detected expected, url
assert_equal expected, Version.parse(url).to_s
end
def assert_version_nil url
assert_nil Version.parse(url)
end
def assert_version_tokens tokens, version
assert_equal tokens, version.send(:tokens).map(&:to_s)
end
end
module FSLeakLogger
def self.included(klass)
require "find"
@@log = File.open("fs_leak_log", "w")
klass.make_my_diffs_pretty!
end
def before_setup
@__files_before_test = []
Find.find(TEST_TMPDIR) { |f| @__files_before_test << f.sub(TEST_TMPDIR, "") }
super
end
def after_teardown
super
files_after_test = []
Find.find(TEST_TMPDIR) { |f| files_after_test << f.sub(TEST_TMPDIR, "") }
if @__files_before_test != files_after_test
@@log.puts location, diff(@__files_before_test, files_after_test)
end
end
end
class TestCase < ::Minitest::Test
include VersionAssertions
include FSLeakLogger if ENV["LOG_FS_LEAKS"]
TEST_SHA1 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef".freeze
TEST_SHA256 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef".freeze
def formula(name="formula_name", path=Formula.path(name), spec=:stable, &block)
@_f = Class.new(Formula, &block).new(name, path, spec)
end
def shutup
err = $stderr.dup
out = $stdout.dup
begin
$stderr.reopen("/dev/null")
$stdout.reopen("/dev/null")
yield
ensure
$stderr.reopen(err)
$stdout.reopen(out)
err.close
out.close
end
end
def assert_nothing_raised
yield
end
def assert_eql(exp, act, msg=nil)
msg = message(msg, "") { diff exp, act }
assert exp.eql?(act), msg
end
def refute_eql(exp, act, msg=nil)
msg = message(msg) {
"Expected #{mu_pp(act)} to not be eql to #{mu_pp(exp)}"
}
refute exp.eql?(act), msg
end
end
end

View File

@ -0,0 +1,426 @@
require 'pathname'
require 'exceptions'
require 'os/mac'
require 'utils/json'
require 'utils/inreplace'
require 'utils/popen'
require 'open-uri'
class Tty
class << self
def blue; bold 34; end
def white; bold 39; end
def red; underline 31; end
def yellow; underline 33; end
def reset; escape 0; end
def em; underline 39; end
def green; bold 32; end
def gray; bold 30; end
def width
`/usr/bin/tput cols`.strip.to_i
end
def truncate(str)
str.to_s[0, width - 4]
end
private
def color n
escape "0;#{n}"
end
def bold n
escape "1;#{n}"
end
def underline n
escape "4;#{n}"
end
def escape n
"\033[#{n}m" if $stdout.tty?
end
end
end
# :startdoc:
def ohai title, *sput
title = Tty.truncate(title) if $stdout.tty? && !ARGV.verbose?
puts "#{Tty.blue}==>#{Tty.white} #{title}#{Tty.reset}"
puts sput
end
def oh1 title
title = Tty.truncate(title) if $stdout.tty? && !ARGV.verbose?
puts "#{Tty.green}==>#{Tty.white} #{title}#{Tty.reset}"
end
def opoo warning
$stderr.puts "#{Tty.red}Warning#{Tty.reset}: #{warning}"
end
def onoe error
$stderr.puts "#{Tty.red}Error#{Tty.reset}: #{error}"
end
def ofail error
onoe error
Homebrew.failed = true
end
def odie error
onoe error
exit 1
end
# :stopdoc:
def pretty_duration s
return "2 seconds" if s < 3 # avoids the plural problem ;)
return "#{s.to_i} seconds" if s < 120
return "%.1f minutes" % (s/60)
end
def plural n, s="s"
(n == 1) ? "" : s
end
def interactive_shell f=nil
unless f.nil?
ENV['HOMEBREW_DEBUG_PREFIX'] = f.prefix
ENV['HOMEBREW_DEBUG_INSTALL'] = f.name
end
Process.wait fork { exec ENV['SHELL'] }
if $?.success?
return
elsif $?.exited?
puts "Aborting due to non-zero exit status"
exit $?.exitstatus
else
raise $?.inspect
end
end
module Homebrew
def self.system cmd, *args
puts "#{cmd} #{args*' '}" if ARGV.verbose?
pid = fork do
yield if block_given?
args.collect!{|arg| arg.to_s}
exec(cmd, *args) rescue nil
exit! 1 # never gets here unless exec failed
end
Process.wait(pid)
$?.success?
end
def self.git_head
HOMEBREW_REPOSITORY.cd { `git rev-parse --verify -q HEAD 2>/dev/null`.chuzzle }
end
end
def with_system_path
old_path = ENV['PATH']
ENV['PATH'] = '/usr/bin:/bin'
yield
ensure
ENV['PATH'] = old_path
end
# Kernel.system but with exceptions
def safe_system cmd, *args
Homebrew.system(cmd, *args) or raise ErrorDuringExecution.new(cmd, args)
end
# prints no output
def quiet_system cmd, *args
Homebrew.system(cmd, *args) do
# Redirect output streams to `/dev/null` instead of closing as some programs
# will fail to execute if they can't write to an open stream.
$stdout.reopen('/dev/null')
$stderr.reopen('/dev/null')
end
end
def curl *args
curl = Pathname.new '/usr/bin/curl'
raise "#{curl} is not executable" unless curl.exist? and curl.executable?
flags = HOMEBREW_CURL_ARGS
flags = flags.delete("#") if ARGV.verbose?
args = [flags, HOMEBREW_USER_AGENT, *args]
# See https://github.com/Homebrew/homebrew/issues/6103
args << "--insecure" if MacOS.version < "10.6"
args << "--verbose" if ENV['HOMEBREW_CURL_VERBOSE']
args << "--silent" unless $stdout.tty?
safe_system curl, *args
end
def puts_columns items, star_items=[]
return if items.empty?
if star_items && star_items.any?
items = items.map{|item| star_items.include?(item) ? "#{item}*" : item}
end
if $stdout.tty?
# determine the best width to display for different console sizes
console_width = `/bin/stty size`.chomp.split(" ").last.to_i
console_width = 80 if console_width <= 0
longest = items.sort_by { |item| item.length }.last
optimal_col_width = (console_width.to_f / (longest.length + 2).to_f).floor
cols = optimal_col_width > 1 ? optimal_col_width : 1
IO.popen("/usr/bin/pr -#{cols} -t -w#{console_width}", "w"){|io| io.puts(items) }
else
puts items
end
end
def which cmd, path=ENV['PATH']
path.split(File::PATH_SEPARATOR).each do |p|
pcmd = File.expand_path(cmd, p)
return Pathname.new(pcmd) if File.file?(pcmd) && File.executable?(pcmd)
end
return nil
end
def which_editor
editor = ENV.values_at('HOMEBREW_EDITOR', 'VISUAL', 'EDITOR').compact.first
# If an editor wasn't set, try to pick a sane default
return editor unless editor.nil?
# Find Textmate
return 'mate' if which "mate"
# Find BBEdit / TextWrangler
return 'edit' if which "edit"
# Find vim
return 'vim' if which "vim"
# Default to standard vim
return '/usr/bin/vim'
end
def exec_editor *args
safe_exec(which_editor, *args)
end
def exec_browser *args
browser = ENV['HOMEBREW_BROWSER'] || ENV['BROWSER'] || OS::PATH_OPEN
safe_exec(browser, *args)
end
def safe_exec cmd, *args
# This buys us proper argument quoting and evaluation
# of environment variables in the cmd parameter.
exec "/bin/sh", "-c", "#{cmd} \"$@\"", "--", *args
end
# GZips the given paths, and returns the gzipped paths
def gzip *paths
paths.collect do |path|
with_system_path { safe_system 'gzip', path }
Pathname.new("#{path}.gz")
end
end
# Returns array of architectures that the given command or library is built for.
def archs_for_command cmd
cmd = which(cmd) unless Pathname.new(cmd).absolute?
Pathname.new(cmd).archs
end
def ignore_interrupts(opt = nil)
std_trap = trap("INT") do
puts "One sec, just cleaning up" unless opt == :quietly
end
yield
ensure
trap("INT", std_trap)
end
def nostdout
if ARGV.verbose?
yield
else
begin
out = $stdout.dup
$stdout.reopen("/dev/null")
yield
ensure
$stdout.reopen(out)
out.close
end
end
end
def paths
@paths ||= ENV['PATH'].split(File::PATH_SEPARATOR).collect do |p|
begin
File.expand_path(p).chomp('/')
rescue ArgumentError
onoe "The following PATH component is invalid: #{p}"
end
end.uniq.compact
end
module GitHub extend self
ISSUES_URI = URI.parse("https://api.github.com/search/issues")
Error = Class.new(RuntimeError)
HTTPNotFoundError = Class.new(Error)
class RateLimitExceededError < Error
def initialize(reset, error)
super <<-EOS.undent
GitHub #{error}
Try again in #{pretty_ratelimit_reset(reset)}, or create an API token:
https://github.com/settings/applications
and then set HOMEBREW_GITHUB_API_TOKEN.
EOS
end
def pretty_ratelimit_reset(reset)
if (seconds = Time.at(reset) - Time.now) > 180
"%d minutes %d seconds" % [seconds / 60, seconds % 60]
else
"#{seconds} seconds"
end
end
end
class AuthenticationFailedError < Error
def initialize(error)
super <<-EOS.undent
GitHub #{error}
HOMEBREW_GITHUB_API_TOKEN may be invalid or expired, check:
https://github.com/settings/applications
EOS
end
end
def open url, headers={}, &block
# This is a no-op if the user is opting out of using the GitHub API.
return if ENV['HOMEBREW_NO_GITHUB_API']
safely_load_net_https
default_headers = {
"User-Agent" => HOMEBREW_USER_AGENT,
"Accept" => "application/vnd.github.v3+json",
}
default_headers['Authorization'] = "token #{HOMEBREW_GITHUB_API_TOKEN}" if HOMEBREW_GITHUB_API_TOKEN
begin
Kernel.open(url, default_headers.merge(headers)) do |f|
yield Utils::JSON.load(f.read)
end
rescue OpenURI::HTTPError => e
handle_api_error(e)
rescue SocketError, OpenSSL::SSL::SSLError => e
raise Error, "Failed to connect to: #{url}\n#{e.message}", e.backtrace
rescue Utils::JSON::Error => e
raise Error, "Failed to parse JSON response\n#{e.message}", e.backtrace
end
end
def handle_api_error(e)
if e.io.meta["x-ratelimit-remaining"].to_i <= 0
reset = e.io.meta.fetch("x-ratelimit-reset").to_i
error = Utils::JSON.load(e.io.read)["message"]
raise RateLimitExceededError.new(reset, error)
end
case e.io.status.first
when "401", "403"
raise AuthenticationFailedError.new(e.message)
when "404"
raise HTTPNotFoundError, e.message, e.backtrace
else
raise Error, e.message, e.backtrace
end
end
def issues_matching(query, qualifiers={})
uri = ISSUES_URI.dup
uri.query = build_query_string(query, qualifiers)
open(uri) { |json| json["items"] }
end
def build_query_string(query, qualifiers)
s = "q=#{uri_escape(query)}+"
s << build_search_qualifier_string(qualifiers)
s << "&per_page=100"
end
def build_search_qualifier_string(qualifiers)
{
:repo => "Homebrew/homebrew",
:in => "title",
}.update(qualifiers).map { |qualifier, value|
"#{qualifier}:#{value}"
}.join("+")
end
def uri_escape(query)
if URI.respond_to?(:encode_www_form_component)
URI.encode_www_form_component(query)
else
require "erb"
ERB::Util.url_encode(query)
end
end
def issues_for_formula name
issues_matching(name, :state => "open")
end
def print_pull_requests_matching(query)
return [] if ENV['HOMEBREW_NO_GITHUB_API']
puts "Searching pull requests..."
open_or_closed_prs = issues_matching(query, :type => "pr")
open_prs = open_or_closed_prs.select {|i| i["state"] == "open" }
if open_prs.any?
puts "Open pull requests:"
prs = open_prs
elsif open_or_closed_prs.any?
puts "Closed pull requests:"
prs = open_or_closed_prs
else
return
end
prs.each { |i| puts "#{i["title"]} (#{i["html_url"]})" }
end
def private_repo?(user, repo)
uri = URI.parse("https://api.github.com/repos/#{user}/#{repo}")
open(uri) { |json| json["private"] }
end
private
# If the zlib formula is loaded, TypeError will be raised when we try to load
# net/https. This monkeypatch prevents that and lets Net::HTTP fall back to
# the non-gzip codepath.
def safely_load_net_https
return if defined?(Net::HTTP)
if defined?(Zlib) && RUBY_VERSION >= "1.9"
require "net/protocol"
http = Class.new(Net::Protocol) do
def self.require(lib)
raise LoadError if lib == "zlib"
super
end
end
Net.const_set(:HTTP, http)
end
require "net/https"
end
end

View File

@ -0,0 +1,33 @@
module Utils
class InreplaceError < RuntimeError
def initialize(errors)
super errors.inject("inreplace failed\n") { |s, (path, errs)|
s << "#{path}:\n" << errs.map { |e| " #{e}\n" }.join
}
end
end
module Inreplace
def inreplace paths, before=nil, after=nil
errors = {}
Array(paths).each do |path|
s = File.open(path, "rb", &:read).extend(StringInreplaceExtension)
if before.nil? && after.nil?
yield s
else
after = after.to_s if Symbol === after
s.gsub!(before, after)
end
errors[path] = s.errors if s.errors.any?
Pathname(path).atomic_write(s)
end
raise InreplaceError.new(errors) if errors.any?
end
module_function :inreplace
end
end

View File

@ -0,0 +1,34 @@
require 'vendor/okjson'
module Utils
module JSON
extend self
Error = Class.new(StandardError)
def load(str)
Vendor::OkJson.decode(str)
rescue Vendor::OkJson::Error => e
raise Error, e.message
end
def dump(obj)
Vendor::OkJson.encode(stringify_keys(obj))
end
def stringify_keys(obj)
case obj
when Array
obj.map { |val| stringify_keys(val) }
when Hash
obj.inject({}) do |result, (key, val)|
key = key.respond_to?(:to_s) ? key.to_s : key
val = stringify_keys(val)
result.merge!(key => val)
end
else
obj
end
end
end
end

View File

@ -0,0 +1,24 @@
module Utils
def self.popen_read(*args, &block)
popen(args, "rb", &block)
end
def self.popen_write(*args, &block)
popen(args, "wb", &block)
end
def self.popen(args, mode)
IO.popen("-", mode) do |pipe|
if pipe
if block_given?
yield pipe
else
return pipe.read
end
else
STDERR.reopen("/dev/null", "w")
exec(*args)
end
end
end
end

View File

@ -0,0 +1,602 @@
# encoding: UTF-8
#
# Copyright 2011, 2012 Keith Rarick
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# See https://github.com/kr/okjson for updates.
require 'stringio'
# Some parts adapted from
# http://golang.org/src/pkg/json/decode.go and
# http://golang.org/src/pkg/utf8/utf8.go
module Vendor
module OkJson
Upstream = '43'
extend self
# Decodes a json document in string s and
# returns the corresponding ruby value.
# String s must be valid UTF-8. If you have
# a string in some other encoding, convert
# it first.
#
# String values in the resulting structure
# will be UTF-8.
def decode(s)
ts = lex(s)
v, ts = textparse(ts)
if ts.length > 0
raise Error, 'trailing garbage'
end
v
end
# Encodes x into a json text. It may contain only
# Array, Hash, String, Numeric, true, false, nil.
# (Note, this list excludes Symbol.)
# X itself must be an Array or a Hash.
# No other value can be encoded, and an error will
# be raised if x contains any other value, such as
# Nan, Infinity, Symbol, and Proc, or if a Hash key
# is not a String.
# Strings contained in x must be valid UTF-8.
def encode(x)
case x
when Hash then objenc(x)
when Array then arrenc(x)
else
raise Error, 'root value must be an Array or a Hash'
end
end
def valenc(x)
case x
when Hash then objenc(x)
when Array then arrenc(x)
when String then strenc(x)
when Numeric then numenc(x)
when true then "true"
when false then "false"
when nil then "null"
else
raise Error, "cannot encode #{x.class}: #{x.inspect}"
end
end
private
# Parses a "json text" in the sense of RFC 4627.
# Returns the parsed value and any trailing tokens.
# Note: this is almost the same as valparse,
# except that it does not accept atomic values.
def textparse(ts)
if ts.length <= 0
raise Error, 'empty'
end
typ, _, val = ts[0]
case typ
when '{' then objparse(ts)
when '[' then arrparse(ts)
else
raise Error, "unexpected #{val.inspect}"
end
end
# Parses a "value" in the sense of RFC 4627.
# Returns the parsed value and any trailing tokens.
def valparse(ts)
if ts.length <= 0
raise Error, 'empty'
end
typ, _, val = ts[0]
case typ
when '{' then objparse(ts)
when '[' then arrparse(ts)
when :val,:str then [val, ts[1..-1]]
else
raise Error, "unexpected #{val.inspect}"
end
end
# Parses an "object" in the sense of RFC 4627.
# Returns the parsed value and any trailing tokens.
def objparse(ts)
ts = eat('{', ts)
obj = {}
if ts[0][0] == '}'
return obj, ts[1..-1]
end
k, v, ts = pairparse(ts)
obj[k] = v
if ts[0][0] == '}'
return obj, ts[1..-1]
end
loop do
ts = eat(',', ts)
k, v, ts = pairparse(ts)
obj[k] = v
if ts[0][0] == '}'
return obj, ts[1..-1]
end
end
end
# Parses a "member" in the sense of RFC 4627.
# Returns the parsed values and any trailing tokens.
def pairparse(ts)
(typ, _, k), ts = ts[0], ts[1..-1]
if typ != :str
raise Error, "unexpected #{k.inspect}"
end
ts = eat(':', ts)
v, ts = valparse(ts)
[k, v, ts]
end
# Parses an "array" in the sense of RFC 4627.
# Returns the parsed value and any trailing tokens.
def arrparse(ts)
ts = eat('[', ts)
arr = []
if ts[0][0] == ']'
return arr, ts[1..-1]
end
v, ts = valparse(ts)
arr << v
if ts[0][0] == ']'
return arr, ts[1..-1]
end
loop do
ts = eat(',', ts)
v, ts = valparse(ts)
arr << v
if ts[0][0] == ']'
return arr, ts[1..-1]
end
end
end
def eat(typ, ts)
if ts[0][0] != typ
raise Error, "expected #{typ} (got #{ts[0].inspect})"
end
ts[1..-1]
end
# Scans s and returns a list of json tokens,
# excluding white space (as defined in RFC 4627).
def lex(s)
ts = []
while s.length > 0
typ, lexeme, val = tok(s)
if typ == nil
raise Error, "invalid character at #{s[0,10].inspect}"
end
if typ != :space
ts << [typ, lexeme, val]
end
s = s[lexeme.length..-1]
end
ts
end
# Scans the first token in s and
# returns a 3-element list, or nil
# if s does not begin with a valid token.
#
# The first list element is one of
# '{', '}', ':', ',', '[', ']',
# :val, :str, and :space.
#
# The second element is the lexeme.
#
# The third element is the value of the
# token for :val and :str, otherwise
# it is the lexeme.
def tok(s)
case s[0]
when ?{ then ['{', s[0,1], s[0,1]]
when ?} then ['}', s[0,1], s[0,1]]
when ?: then [':', s[0,1], s[0,1]]
when ?, then [',', s[0,1], s[0,1]]
when ?[ then ['[', s[0,1], s[0,1]]
when ?] then [']', s[0,1], s[0,1]]
when ?n then nulltok(s)
when ?t then truetok(s)
when ?f then falsetok(s)
when ?" then strtok(s)
when Spc, ?\t, ?\n, ?\r then [:space, s[0,1], s[0,1]]
else
numtok(s)
end
end
def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
def numtok(s)
m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
if m && m.begin(0) == 0
if !m[2] && !m[3]
[:val, m[0], Integer(m[0])]
elsif m[2]
[:val, m[0], Float(m[0])]
else
[:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
end
else
[]
end
end
def strtok(s)
m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
if ! m
raise Error, "invalid string literal at #{abbrev(s)}"
end
[:str, m[0], unquote(m[0])]
end
def abbrev(s)
t = s[0,10]
p = t['`']
t = t[0,p] if p
t = t + '...' if t.length < s.length
'`' + t + '`'
end
# Converts a quoted json string literal q into a UTF-8-encoded string.
# The rules are different than for Ruby, so we cannot use eval.
# Unquote will raise an error if q contains control characters.
def unquote(q)
q = q[1...-1]
a = q.dup # allocate a big enough string
# In ruby >= 1.9, a[w] is a codepoint, not a byte.
if rubydoesenc?
a.force_encoding('UTF-8')
end
r, w = 0, 0
while r < q.length
c = q[r]
if c == ?\\
r += 1
if r >= q.length
raise Error, "string literal ends with a \"\\\": \"#{q}\""
end
case q[r]
when ?",?\\,?/,?'
a[w] = q[r]
r += 1
w += 1
when ?b,?f,?n,?r,?t
a[w] = Unesc[q[r]]
r += 1
w += 1
when ?u
r += 1
uchar = begin
hexdec4(q[r,4])
rescue RuntimeError => e
raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
end
r += 4
if surrogate? uchar
if q.length >= r+6
uchar1 = hexdec4(q[r+2,4])
uchar = subst(uchar, uchar1)
if uchar != Ucharerr
# A valid pair; consume.
r += 6
end
end
end
if rubydoesenc?
a[w] = '' << uchar
w += 1
else
w += ucharenc(a, w, uchar)
end
else
raise Error, "invalid escape char #{q[r]} in \"#{q}\""
end
elsif c == ?" || c < Spc
raise Error, "invalid character in string literal \"#{q}\""
else
# Copy anything else byte-for-byte.
# Valid UTF-8 will remain valid UTF-8.
# Invalid UTF-8 will remain invalid UTF-8.
# In ruby >= 1.9, c is a codepoint, not a byte,
# in which case this is still what we want.
a[w] = c
r += 1
w += 1
end
end
a[0,w]
end
# Encodes unicode character u as UTF-8
# bytes in string a at position i.
# Returns the number of bytes written.
def ucharenc(a, i, u)
if u <= Uchar1max
a[i] = (u & 0xff).chr
1
elsif u <= Uchar2max
a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
a[i+1] = (Utagx | (u&Umaskx)).chr
2
elsif u <= Uchar3max
a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
a[i+2] = (Utagx | (u&Umaskx)).chr
3
else
a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
a[i+3] = (Utagx | (u&Umaskx)).chr
4
end
end
def hexdec4(s)
if s.length != 4
raise Error, 'short'
end
(nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
end
def subst(u1, u2)
if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
end
return Ucharerr
end
def surrogate?(u)
Usurr1 <= u && u < Usurr3
end
def nibble(c)
if ?0 <= c && c <= ?9 then c.ord - ?0.ord
elsif ?a <= c && c <= ?z then c.ord - ?a.ord + 10
elsif ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
else
raise Error, "invalid hex code #{c}"
end
end
def objenc(x)
'{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
end
def arrenc(a)
'[' + a.map{|x| valenc(x)}.join(',') + ']'
end
def keyenc(k)
case k
when String then strenc(k)
else
raise Error, "Hash key is not a string: #{k.inspect}"
end
end
def strenc(s)
t = StringIO.new
t.putc(?")
r = 0
while r < s.length
case s[r]
when ?" then t.print('\\"')
when ?\\ then t.print('\\\\')
when ?\b then t.print('\\b')
when ?\f then t.print('\\f')
when ?\n then t.print('\\n')
when ?\r then t.print('\\r')
when ?\t then t.print('\\t')
else
c = s[r]
# In ruby >= 1.9, s[r] is a codepoint, not a byte.
if rubydoesenc?
begin
# c.ord will raise an error if c is invalid UTF-8
if c.ord < Spc.ord
c = "\\u%04x" % [c.ord]
end
t.write(c)
rescue
t.write(Ustrerr)
end
elsif c < Spc
t.write("\\u%04x" % c)
elsif Spc <= c && c <= ?~
t.putc(c)
else
n = ucharcopy(t, s, r) # ensure valid UTF-8 output
r += n - 1 # r is incremented below
end
end
r += 1
end
t.putc(?")
t.string
end
def numenc(x)
if ((x.nan? || x.infinite?) rescue false)
raise Error, "Numeric cannot be represented: #{x}"
end
"#{x}"
end
# Copies the valid UTF-8 bytes of a single character
# from string s at position i to I/O object t, and
# returns the number of bytes copied.
# If no valid UTF-8 char exists at position i,
# ucharcopy writes Ustrerr and returns 1.
def ucharcopy(t, s, i)
n = s.length - i
raise Utf8Error if n < 1
c0 = s[i].ord
# 1-byte, 7-bit sequence?
if c0 < Utagx
t.putc(c0)
return 1
end
raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
raise Utf8Error if n < 2 # need continuation byte
c1 = s[i+1].ord
raise Utf8Error if c1 < Utagx || Utag2 <= c1
# 2-byte, 11-bit sequence?
if c0 < Utag3
raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
t.putc(c0)
t.putc(c1)
return 2
end
# need second continuation byte
raise Utf8Error if n < 3
c2 = s[i+2].ord
raise Utf8Error if c2 < Utagx || Utag2 <= c2
# 3-byte, 16-bit sequence?
if c0 < Utag4
u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
raise Utf8Error if u <= Uchar2max
t.putc(c0)
t.putc(c1)
t.putc(c2)
return 3
end
# need third continuation byte
raise Utf8Error if n < 4
c3 = s[i+3].ord
raise Utf8Error if c3 < Utagx || Utag2 <= c3
# 4-byte, 21-bit sequence?
if c0 < Utag5
u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
raise Utf8Error if u <= Uchar3max
t.putc(c0)
t.putc(c1)
t.putc(c2)
t.putc(c3)
return 4
end
raise Utf8Error
rescue Utf8Error
t.write(Ustrerr)
return 1
end
def rubydoesenc?
::String.method_defined?(:force_encoding)
end
class Utf8Error < ::StandardError
end
class Error < ::StandardError
end
Utagx = 0b1000_0000
Utag2 = 0b1100_0000
Utag3 = 0b1110_0000
Utag4 = 0b1111_0000
Utag5 = 0b1111_1000
Umaskx = 0b0011_1111
Umask2 = 0b0001_1111
Umask3 = 0b0000_1111
Umask4 = 0b0000_0111
Uchar1max = (1<<7) - 1
Uchar2max = (1<<11) - 1
Uchar3max = (1<<16) - 1
Ucharerr = 0xFFFD # unicode "replacement char"
Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
Usurrself = 0x10000
Usurr1 = 0xd800
Usurr2 = 0xdc00
Usurr3 = 0xe000
Spc = ' '[0]
Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
end
end

View File

@ -0,0 +1,320 @@
class Version
include Comparable
class Token
include Comparable
attr_reader :value
def initialize(value)
@value = value
end
def inspect
"#<#{self.class.name} #{value.inspect}>"
end
def to_s
value.to_s
end
end
class NullToken < Token
def initialize(value=nil)
super
end
def <=>(other)
case other
when NumericToken
other.value == 0 ? 0 : -1
when AlphaToken, BetaToken, RCToken
1
else
-1
end
end
def inspect
"#<#{self.class.name}>"
end
end
NULL_TOKEN = NullToken.new
class StringToken < Token
PATTERN = /[a-z]+[0-9]*/i
def initialize(value)
@value = value.to_s
end
def <=>(other)
case other
when StringToken
value <=> other.value
when NumericToken, NullToken
-Integer(other <=> self)
end
end
end
class NumericToken < Token
PATTERN = /[0-9]+/i
def initialize(value)
@value = value.to_i
end
def <=>(other)
case other
when NumericToken
value <=> other.value
when StringToken
1
when NullToken
-Integer(other <=> self)
end
end
end
class CompositeToken < StringToken
def rev
value[/([0-9]+)/, 1] || "0"
end
end
class AlphaToken < CompositeToken
PATTERN = /a(?:lpha)?[0-9]*/i
def <=>(other)
case other
when AlphaToken
rev <=> other.rev
else
super
end
end
end
class BetaToken < CompositeToken
PATTERN = /b(?:eta)?[0-9]*/i
def <=>(other)
case other
when BetaToken
rev <=> other.rev
when AlphaToken
1
when RCToken, PatchToken
-1
else
super
end
end
end
class RCToken < CompositeToken
PATTERN = /rc[0-9]*/i
def <=>(other)
case other
when RCToken
rev <=> other.rev
when AlphaToken, BetaToken
1
when PatchToken
-1
else
super
end
end
end
class PatchToken < CompositeToken
PATTERN = /p[0-9]*/i
def <=>(other)
case other
when PatchToken
rev <=> other.rev
when AlphaToken, BetaToken, RCToken
1
else
super
end
end
end
SCAN_PATTERN = Regexp.union(
AlphaToken::PATTERN,
BetaToken::PATTERN,
RCToken::PATTERN,
PatchToken::PATTERN,
NumericToken::PATTERN,
StringToken::PATTERN
)
def self.detect(url, specs={})
if specs.has_key?(:tag)
new(specs[:tag][/((?:\d+\.)*\d+)/, 1], true)
else
parse(url)
end
end
def initialize(val, detected=false)
if val.respond_to?(:to_str)
@version = val.to_str
else
raise TypeError, "Version value must be a string"
end
@detected_from_url = detected
end
def detected_from_url?
@detected_from_url
end
def head?
@version == 'HEAD'
end
def <=>(other)
return unless Version === other
return 0 if head? && other.head?
return 1 if head? && !other.head?
return -1 if !head? && other.head?
max = [tokens.length, other.tokens.length].max
pad_to(max) <=> other.pad_to(max)
end
alias_method :eql?, :==
def hash
@version.hash
end
def to_s
@version.dup
end
alias_method :to_str, :to_s
protected
def begins_with_numeric?
NumericToken === tokens.first
end
def pad_to(length)
if begins_with_numeric?
nums, rest = tokens.partition { |t| NumericToken === t }
nums.fill(NULL_TOKEN, nums.length, length - tokens.length)
nums.concat(rest)
else
tokens.dup.fill(NULL_TOKEN, tokens.length, length - tokens.length)
end
end
def tokens
@tokens ||= tokenize
end
def tokenize
@version.scan(SCAN_PATTERN).map! do |token|
case token
when /\A#{AlphaToken::PATTERN}\z/o then AlphaToken
when /\A#{BetaToken::PATTERN}\z/o then BetaToken
when /\A#{RCToken::PATTERN}\z/o then RCToken
when /\A#{PatchToken::PATTERN}\z/o then PatchToken
when /\A#{NumericToken::PATTERN}\z/o then NumericToken
when /\A#{StringToken::PATTERN}\z/o then StringToken
end.new(token)
end
end
def self.parse spec
version = _parse(spec)
Version.new(version, true) unless version.nil?
end
def self._parse spec
spec = Pathname.new(spec) unless spec.is_a? Pathname
spec_s = spec.to_s
stem = if spec.directory?
spec.basename.to_s
elsif %r[((?:sourceforge.net|sf.net)/.*)/download$].match(spec_s)
Pathname.new(spec.dirname).stem
else
spec.stem
end
# GitHub tarballs
# e.g. https://github.com/foo/bar/tarball/v1.2.3
# e.g. https://github.com/sam-github/libnet/tarball/libnet-1.1.4
# e.g. https://github.com/isaacs/npm/tarball/v0.2.5-1
# e.g. https://github.com/petdance/ack/tarball/1.93_02
m = %r[github.com/.+/(?:zip|tar)ball/(?:v|\w+-)?((?:\d+[-._])+\d*)$].match(spec_s)
return m.captures.first unless m.nil?
# e.g. https://github.com/erlang/otp/tarball/OTP_R15B01 (erlang style)
m = /[-_]([Rr]\d+[AaBb]\d*(?:-\d+)?)/.match(spec_s)
return m.captures.first unless m.nil?
# e.g. boost_1_39_0
m = /((?:\d+_)+\d+)$/.match(stem)
return m.captures.first.gsub('_', '.') unless m.nil?
# e.g. foobar-4.5.1-1
# e.g. ruby-1.9.1-p243
m = /-((?:\d+\.)*\d\.\d+-(?:p|rc|RC)?\d+)(?:[-._](?:bin|dist|stable|src|sources))?$/.match(stem)
return m.captures.first unless m.nil?
# e.g. lame-398-1
m = /-((?:\d)+-\d)/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar-4.5.1
m = /-((?:\d+\.)*\d+)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar-4.5.1b
m = /-((?:\d+\.)*\d+(?:[abc]|rc|RC)\d*)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar-4.5.0-beta1, or foobar-4.50-beta
m = /-((?:\d+\.)*\d+-beta\d*)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar4.5.1
m = /((?:\d+\.)*\d+)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar-4.5.0-bin
m = /-((?:\d+\.)+\d+[abc]?)[-._](?:bin|dist|stable|src|sources?)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. dash_0.5.5.1.orig.tar.gz (Debian style)
m = /_((?:\d+\.)+\d+[abc]?)[.]orig$/.match(stem)
return m.captures.first unless m.nil?
# e.g. http://www.openssl.org/source/openssl-0.9.8s.tar.gz
m = /-v?([^-]+)/.match(stem)
return m.captures.first unless m.nil?
# e.g. astyle_1.23_macosx.tar.gz
m = /_([^_]+)/.match(stem)
return m.captures.first unless m.nil?
# e.g. http://mirrors.jenkins-ci.org/war/1.486/jenkins.war
m = /\/(\d\.\d+(\.\d)?)\//.match(spec_s)
return m.captures.first unless m.nil?
# e.g. http://www.ijg.org/files/jpegsrc.v8d.tar.gz
m = /\.v(\d+[a-z]?)/.match(stem)
return m.captures.first unless m.nil?
end
end

View File

@ -0,0 +1,4 @@
A temporary minimal fork of the Homebrew codebase.
This is a transitional measure while we separate the Homebrew-cask backend
from Homebrew.