check in a somewhat stripped fork of Homebrew
under lib/homebrew-fork
This commit is contained in:
parent
cd720285e1
commit
9868944de5
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
require 'compat/fails_with_llvm'
|
||||
require 'compat/formula'
|
||||
require 'compat/hardware'
|
||||
require 'compat/macos'
|
||||
require 'compat/md5'
|
||||
require 'compat/version'
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
class Symbol
|
||||
def to_proc
|
||||
proc { |*args| args.shift.send(self, *args) }
|
||||
end unless method_defined?(:to_proc)
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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",
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
Loading…
Reference in New Issue