Merge branch 'master' of github.com:Homebrew/brew into update-compiler-docs

This commit is contained in:
Afnan Enayet 2018-07-05 17:23:09 -04:00
commit fd84bcbff7
No known key found for this signature in database
GPG Key ID: 9C669708328BC5A4
45 changed files with 1003 additions and 401 deletions

View File

@ -1,20 +1,22 @@
language: c language: ruby
rvm: system
cache: cache:
directories: directories:
- $HOME/Library/Caches/Homebrew/style - $HOME/Library/Caches/Homebrew/style
- $HOME/Library/Caches/Homebrew/tests - $HOME/Library/Caches/Homebrew/tests
- Library/Homebrew/vendor/bundle - Library/Homebrew/vendor/bundle
branches: branches:
only: only:
- master - master
matrix: matrix:
fast_finish: true fast_finish: true
include: include:
- os: osx - os: osx
compiler: clang osx_image: xcode9.4
osx_image: xcode9.2
- os: linux - os: linux
compiler: gcc
sudo: false sudo: false
before_install: before_install:

View File

@ -104,10 +104,8 @@ then
HOMEBREW_FORCE_BREWED_GIT="1" HOMEBREW_FORCE_BREWED_GIT="1"
fi fi
if [[ -z "$HOMEBREW_CACHE" ]] HOMEBREW_CACHE="${HOMEBREW_CACHE:-${HOME}/Library/Caches/Homebrew}"
then HOMEBREW_SYSTEM_TEMP="/private/tmp"
HOMEBREW_CACHE="$HOME/Library/Caches/Homebrew"
fi
else else
HOMEBREW_PROCESSOR="$(uname -m)" HOMEBREW_PROCESSOR="$(uname -m)"
HOMEBREW_PRODUCT="${HOMEBREW_SYSTEM}brew" HOMEBREW_PRODUCT="${HOMEBREW_SYSTEM}brew"
@ -115,17 +113,13 @@ else
: "${HOMEBREW_OS_VERSION:=$(uname -r)}" : "${HOMEBREW_OS_VERSION:=$(uname -r)}"
HOMEBREW_OS_USER_AGENT_VERSION="$HOMEBREW_OS_VERSION" HOMEBREW_OS_USER_AGENT_VERSION="$HOMEBREW_OS_VERSION"
if [[ -z "$HOMEBREW_CACHE" ]] CACHE_HOME="${XDG_CACHE_HOME:-${HOME}/.cache}"
then HOMEBREW_CACHE="${HOMEBREW_CACHE:-${CACHE_HOME}/Homebrew}"
if [[ -n "$XDG_CACHE_HOME" ]] HOMEBREW_SYSTEM_TEMP="/tmp"
then
HOMEBREW_CACHE="$XDG_CACHE_HOME/Homebrew"
else
HOMEBREW_CACHE="$HOME/.cache/Homebrew"
fi
fi
fi fi
HOMEBREW_TEMP="${HOMEBREW_TEMP:-${HOMEBREW_SYSTEM_TEMP}}"
if [[ -n "$HOMEBREW_FORCE_BREWED_CURL" && if [[ -n "$HOMEBREW_FORCE_BREWED_CURL" &&
-x "$HOMEBREW_PREFIX/opt/curl/bin/curl" ]] && -x "$HOMEBREW_PREFIX/opt/curl/bin/curl" ]] &&
"$HOMEBREW_PREFIX/opt/curl/bin/curl" --version >/dev/null "$HOMEBREW_PREFIX/opt/curl/bin/curl" --version >/dev/null
@ -153,6 +147,8 @@ export HOMEBREW_BREW_FILE
export HOMEBREW_PREFIX export HOMEBREW_PREFIX
export HOMEBREW_REPOSITORY export HOMEBREW_REPOSITORY
export HOMEBREW_LIBRARY export HOMEBREW_LIBRARY
export HOMEBREW_SYSTEM_TEMP
export HOMEBREW_TEMP
# Declared in brew.sh # Declared in brew.sh
export HOMEBREW_VERSION export HOMEBREW_VERSION
@ -309,6 +305,21 @@ EOS
} }
check-run-command-as-root check-run-command-as-root
check-prefix-is-not-tmpdir() {
[[ -z "${HOMEBREW_MACOS}" ]] && return
if [[ "${HOMEBREW_PREFIX}" = "${HOMEBREW_TEMP}"* ]]
then
odie <<EOS
Your HOMEBREW_PREFIX is in the Homebrew temporary directory, which Homebrew
uses to store downloads and builds. You can resolve this by installing Homebrew to
either the standard prefix (/usr/local) or to a non-standard prefix that is not
in the Homebrew temporary directory.
EOS
fi
}
check-prefix-is-not-tmpdir
if [[ "$HOMEBREW_PREFIX" = "/usr/local" && if [[ "$HOMEBREW_PREFIX" = "/usr/local" &&
"$HOMEBREW_PREFIX" != "$HOMEBREW_REPOSITORY" && "$HOMEBREW_PREFIX" != "$HOMEBREW_REPOSITORY" &&
"$HOMEBREW_CELLAR" = "$HOMEBREW_REPOSITORY/Cellar" ]] "$HOMEBREW_CELLAR" = "$HOMEBREW_REPOSITORY/Cellar" ]]

View File

@ -201,21 +201,20 @@ module Hbc
def check_hosting_with_appcast def check_hosting_with_appcast
return if cask.appcast return if cask.appcast
check_github_releases_appcast
check_sourceforge_appcast
end
def check_github_releases_appcast add_appcast = "please add an appcast. See https://github.com/Homebrew/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/appcast.md"
return unless cask.url.to_s =~ %r{github.com/([^/]+)/([^/]+)/releases/download/(\S+)}
add_warning "Download uses GitHub releases, please add an appcast. See https://github.com/Homebrew/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/appcast.md" case cask.url.to_s
end when %r{github.com/([^/]+)/([^/]+)/releases/download/(\S+)}
add_warning "Download uses GitHub releases, #{add_appcast}"
def check_sourceforge_appcast when %r{sourceforge.net/(\S+)}
return if cask.version.latest? return if cask.version.latest?
return unless cask.url.to_s =~ %r{sourceforge.net/(\S+)} add_warning "Download is hosted on SourceForge, #{add_appcast}"
when %r{dl.devmate.com/(\S+)}
add_warning "Download is hosted on SourceForge, please add an appcast. See https://github.com/Homebrew/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/appcast.md" add_warning "Download is hosted on DevMate, #{add_appcast}"
when %r{rink.hockeyapp.net/(\S+)}
add_warning "Download is hosted on HockeyApp, #{add_appcast}"
end
end end
def check_url def check_url

View File

@ -62,13 +62,11 @@ module Homebrew
if tap.installed? if tap.installed?
info += tap.pinned? ? "pinned" : "unpinned" info += tap.pinned? ? "pinned" : "unpinned"
info += ", private" if tap.private? info += ", private" if tap.private?
if (formula_count = tap.formula_files.size).positive? info += if (contents = tap.contents).empty?
info += ", #{Formatter.pluralize(formula_count, "formula")}" ", no commands/casks/formulae"
else
", #{contents.join(", ")}"
end end
if (command_count = tap.command_files.size).positive?
info += ", #{Formatter.pluralize(command_count, "command")}"
end
info += ", no formulae/commands" if (formula_count + command_count).zero?
info += "\n#{tap.path} (#{tap.path.abv})" info += "\n#{tap.path} (#{tap.path.abv})"
info += "\nFrom: #{tap.remote.nil? ? "N/A" : tap.remote}" info += "\nFrom: #{tap.remote.nil? ? "N/A" : tap.remote}"
else else

View File

@ -39,7 +39,13 @@ HOMEBREW_CACHE_FORMULA = HOMEBREW_CACHE/"Formula"
HOMEBREW_LOGS = Pathname.new(ENV["HOMEBREW_LOGS"] || "~/Library/Logs/Homebrew/").expand_path HOMEBREW_LOGS = Pathname.new(ENV["HOMEBREW_LOGS"] || "~/Library/Logs/Homebrew/").expand_path
# Must use /tmp instead of $TMPDIR because long paths break Unix domain sockets # Must use /tmp instead of $TMPDIR because long paths break Unix domain sockets
HOMEBREW_TEMP = Pathname.new(ENV.fetch("HOMEBREW_TEMP", "/tmp")) HOMEBREW_TEMP = begin
# /tmp fallback is here for people auto-updating from a version where
# HOMEBREW_TEMP isn't set.
tmp = Pathname.new(ENV["HOMEBREW_TEMP"] || "/tmp")
tmp.mkpath unless tmp.exist?
tmp.realpath
end
unless defined? HOMEBREW_LIBRARY_PATH unless defined? HOMEBREW_LIBRARY_PATH
# Root of the Homebrew code base # Root of the Homebrew code base

View File

@ -216,7 +216,7 @@ module Homebrew
def initialize(formula, options = {}) def initialize(formula, options = {})
@formula = formula @formula = formula
@new_formula = options[:new_formula] @new_formula = options[:new_formula] && !formula.versioned_formula?
@strict = options[:strict] @strict = options[:strict]
@online = options[:online] @online = options[:online]
@display_cop_names = options[:display_cop_names] @display_cop_names = options[:display_cop_names]
@ -236,6 +236,7 @@ module Homebrew
return unless @style_offenses return unless @style_offenses
@style_offenses.each do |offense| @style_offenses.each do |offense|
if offense.cop_name.start_with?("NewFormulaAudit") if offense.cop_name.start_with?("NewFormulaAudit")
next if formula.versioned_formula?
new_formula_problem offense.to_s(display_cop_name: @display_cop_names) new_formula_problem offense.to_s(display_cop_name: @display_cop_names)
next next
end end
@ -416,7 +417,6 @@ module Homebrew
end end
next unless @new_formula next unless @new_formula
next if formula.versioned_formula?
next unless @official_tap next unless @official_tap
if dep.tags.include?(:recommended) || dep.tags.include?(:optional) if dep.tags.include?(:recommended) || dep.tags.include?(:optional)
new_formula_problem "Formulae should not have #{dep.tags} dependencies." new_formula_problem "Formulae should not have #{dep.tags} dependencies."
@ -571,8 +571,19 @@ module Homebrew
end end
end end
if @new_formula && formula.head if formula.head || formula.devel
new_formula_problem "Formulae should not have a HEAD spec" unstable_spec_message = "Formulae should not have an unstable spec"
if @new_formula
new_formula_problem unstable_spec_message
elsif formula.versioned_formula?
versioned_unstable_spec = %w[
bash-completion@2
imagemagick@6
openssl@1.1
python@2
]
problem unstable_spec_message unless versioned_unstable_spec.include?(formula.name)
end
end end
throttled = %w[ throttled = %w[
@ -586,7 +597,7 @@ module Homebrew
throttled.each_slice(2).to_a.map do |a, b| throttled.each_slice(2).to_a.map do |a, b|
next if formula.stable.nil? next if formula.stable.nil?
version = formula.stable.version.to_s.split(".").last.to_i version = formula.stable.version.to_s.split(".").last.to_i
if @strict && a.include?(formula.name) && version.modulo(b.to_i).nonzero? if @strict && a == formula.name && version.modulo(b.to_i).nonzero?
problem "should only be updated every #{b} releases on multiples of #{b}" problem "should only be updated every #{b} releases on multiples of #{b}"
end end
end end

View File

@ -31,14 +31,15 @@ module Homebrew
ENV["HOMEBREW_UPDATE_TEST"] = "1" ENV["HOMEBREW_UPDATE_TEST"] = "1"
if args.to_tag? branch = if args.to_tag?
ENV["HOMEBREW_UPDATE_TO_TAG"] = "1" ENV["HOMEBREW_UPDATE_TO_TAG"] = "1"
branch = "stable" "stable"
else else
branch = "master" "master"
end end
cd HOMEBREW_REPOSITORY start_commit, end_commit = nil
cd HOMEBREW_REPOSITORY do
start_commit = if commit = args.commit start_commit = if commit = args.commit
commit commit
elsif date = args.before elsif date = args.before
@ -68,12 +69,12 @@ module Homebrew
end_commit = Utils.popen_read("git", "rev-parse", "HEAD").chomp end_commit = Utils.popen_read("git", "rev-parse", "HEAD").chomp
odie "Could not find end commit!" if end_commit.empty? odie "Could not find end commit!" if end_commit.empty?
end
puts "Start commit: #{start_commit}" puts "Start commit: #{start_commit}"
puts "End commit: #{end_commit}" puts "End commit: #{end_commit}"
mktemp("update-test") do |staging| mkdir "update-test" do
staging.retain! if args.keep_tmp?
curdir = Pathname.new(Dir.pwd) curdir = Pathname.new(Dir.pwd)
oh1 "Setup test environment..." oh1 "Setup test environment..."
@ -107,5 +108,7 @@ module Homebrew
EOS EOS
end end
end end
ensure
FileUtils.rm_r "update-test" unless args.keep_tmp?
end end
end end

View File

@ -90,53 +90,6 @@ class AbstractDownloadStrategy
def quiet_safe_system(*args) def quiet_safe_system(*args)
safe_system(*expand_safe_system_args(args)) safe_system(*expand_safe_system_args(args))
end end
private
def xzpath
"#{HOMEBREW_PREFIX}/opt/xz/bin/xz"
end
def lzippath
"#{HOMEBREW_PREFIX}/opt/lzip/bin/lzip"
end
def lhapath
"#{HOMEBREW_PREFIX}/opt/lha/bin/lha"
end
def cvspath
@cvspath ||= %W[
/usr/bin/cvs
#{HOMEBREW_PREFIX}/bin/cvs
#{HOMEBREW_PREFIX}/opt/cvs/bin/cvs
#{which("cvs")}
].find { |p| File.executable? p }
end
def hgpath
@hgpath ||= %W[
#{which("hg")}
#{HOMEBREW_PREFIX}/bin/hg
#{HOMEBREW_PREFIX}/opt/mercurial/bin/hg
].find { |p| File.executable? p }
end
def bzrpath
@bzrpath ||= %W[
#{which("bzr")}
#{HOMEBREW_PREFIX}/bin/bzr
#{HOMEBREW_PREFIX}/opt/bazaar/bin/bzr
].find { |p| File.executable? p }
end
def fossilpath
@fossilpath ||= %W[
#{which("fossil")}
#{HOMEBREW_PREFIX}/bin/fossil
#{HOMEBREW_PREFIX}/opt/fossil/bin/fossil
].find { |p| File.executable? p }
end
end end
class VCSDownloadStrategy < AbstractDownloadStrategy class VCSDownloadStrategy < AbstractDownloadStrategy
@ -224,43 +177,39 @@ end
class AbstractFileDownloadStrategy < AbstractDownloadStrategy class AbstractFileDownloadStrategy < AbstractDownloadStrategy
def stage def stage
case type = cached_location.compression_type path = cached_location
unpack_dir = Pathname.pwd
case type = path.compression_type
when :zip when :zip
quiet_safe_system "unzip", "-qq", cached_location safe_system "unzip", "-qq", path, "-d", unpack_dir
chdir chdir
when :gzip_only when :gzip_only
buffered_write "gunzip" FileUtils.cp path, unpack_dir, preserve: true
safe_system "gunzip", "-q", "-N", unpack_dir/path.basename
when :bzip2_only when :bzip2_only
buffered_write "bunzip2" FileUtils.cp path, unpack_dir, preserve: true
safe_system "bunzip2", "-q", unpack_dir/path.basename
when :gzip, :bzip2, :xz, :compress, :tar when :gzip, :bzip2, :xz, :compress, :tar
tar_flags = "x"
if type == :gzip
tar_flags << "z"
elsif type == :bzip2
tar_flags << "j"
elsif type == :xz
tar_flags << "J"
end
tar_flags << "f"
if type == :xz && DependencyCollector.tar_needs_xz_dependency? if type == :xz && DependencyCollector.tar_needs_xz_dependency?
pipe_to_tar xzpath pipe_to_tar "#{HOMEBREW_PREFIX}/opt/xz/bin/xz", unpack_dir
else else
safe_system "tar", tar_flags, cached_location safe_system "tar", "xf", path, "-C", unpack_dir
end end
chdir chdir
when :lzip when :lzip
pipe_to_tar lzippath pipe_to_tar "#{HOMEBREW_PREFIX}/opt/lzip/bin/lzip", unpack_dir
chdir chdir
when :lha when :lha
safe_system lhapath, "x", cached_location safe_system "#{HOMEBREW_PREFIX}/opt/lha/bin/lha", "xq2w=#{unpack_dir}", path
when :xar when :xar
safe_system "/usr/bin/xar", "-xf", cached_location safe_system "xar", "-x", "-f", path, "-C", unpack_dir
when :rar when :rar
quiet_safe_system "unrar", "x", "-inul", cached_location safe_system "unrar", "x", "-inul", path, unpack_dir
when :p7zip when :p7zip
safe_system "7zr", "x", cached_location safe_system "7zr", "x", "-y", "-bd", "-bso0", path, "-o#{unpack_dir}"
else else
cp cached_location, basename_without_params, preserve: true cp path, unpack_dir/basename_without_params, preserve: true
end end
end end
@ -278,29 +227,17 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy
end end
end end
def pipe_to_tar(tool) def pipe_to_tar(tool, unpack_dir)
Utils.popen_read(tool, "-dc", cached_location.to_s) do |rd| path = cached_location
Utils.popen_write("tar", "xf", "-") do |wr|
Utils.popen_read(tool, "-dc", path) do |rd|
Utils.popen_write("tar", "xf", "-", "-C", unpack_dir) do |wr|
buf = "" buf = ""
wr.write(buf) while rd.read(16384, buf) wr.write(buf) while rd.read(16384, buf)
end end
end end
end 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, cached_location.extname)
Utils.popen_read(tool, "-f", cached_location.to_s, "-c") do |pipe|
File.open(target, "wb") do |f|
buf = ""
f.write(buf) while pipe.read(16384, buf)
end
end
end
def basename_without_params def basename_without_params
# Strip any ?thing=wad out of .c?thing=wad style extensions # Strip any ?thing=wad out of .c?thing=wad style extensions
File.basename(@url)[/[^?]+/] File.basename(@url)[/[^?]+/]
@ -672,7 +609,7 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
def stage def stage
super super
quiet_safe_system "svn", "export", "--force", cached_location, Dir.pwd safe_system "svn", "export", "--force", cached_location, Dir.pwd
end end
def source_modified_time def source_modified_time
@ -849,9 +786,9 @@ class GitDownloadStrategy < VCSDownloadStrategy
return unless @ref_type == :branch || !ref? return unless @ref_type == :branch || !ref?
if !shallow_clone? && shallow_dir? if !shallow_clone? && shallow_dir?
quiet_safe_system "git", "fetch", "origin", "--unshallow" safe_system "git", "fetch", "origin", "--unshallow"
else else
quiet_safe_system "git", "fetch", "origin" safe_system "git", "fetch", "origin"
end end
end end
@ -866,7 +803,7 @@ class GitDownloadStrategy < VCSDownloadStrategy
def checkout def checkout
ohai "Checking out #{@ref_type} #{@ref}" if @ref_type && @ref ohai "Checking out #{@ref_type} #{@ref}" if @ref_type && @ref
quiet_safe_system "git", "checkout", "-f", @ref, "--" safe_system "git", "checkout", "-f", @ref, "--"
end end
def reset_args def reset_args
@ -881,12 +818,12 @@ class GitDownloadStrategy < VCSDownloadStrategy
end end
def reset def reset
quiet_safe_system "git", *reset_args safe_system "git", *reset_args
end end
def update_submodules def update_submodules
quiet_safe_system "git", "submodule", "foreach", "--recursive", "git submodule sync" safe_system "git", "submodule", "foreach", "--recursive", "git submodule sync"
quiet_safe_system "git", "submodule", "update", "--init", "--recursive" safe_system "git", "submodule", "update", "--init", "--recursive"
fix_absolute_submodule_gitdir_references! fix_absolute_submodule_gitdir_references!
end end
@ -992,6 +929,10 @@ class CVSDownloadStrategy < VCSDownloadStrategy
end end
end end
def cvspath
@cvspath ||= which("cvs", PATH.new("/usr/bin", Formula["cvs"].opt_bin, ENV["PATH"]))
end
def source_modified_time def source_modified_time
# Filter CVS's files because the timestamp for each of them is the moment # Filter CVS's files because the timestamp for each of them is the moment
# of clone. # of clone.
@ -1045,6 +986,10 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
@url = @url.sub(%r{^hg://}, "") @url = @url.sub(%r{^hg://}, "")
end end
def hgpath
@hgpath ||= which("hg", PATH.new(Formula["mercurial"].opt_bin, ENV["PATH"]))
end
def stage def stage
super super
@ -1082,7 +1027,9 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
end end
def update def update
cached_location.cd { quiet_safe_system hgpath, "pull", "--update" } cached_location.cd do
safe_system hgpath, "pull", "--update"
end
end end
end end
@ -1093,6 +1040,10 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
ENV["BZR_HOME"] = HOMEBREW_TEMP ENV["BZR_HOME"] = HOMEBREW_TEMP
end end
def bzrpath
@bzrpath ||= which("bzr", PATH.new(Formula["bazaar"].opt_bin, ENV["PATH"]))
end
def stage def stage
# The export command doesn't work on checkouts # The export command doesn't work on checkouts
# See https://bugs.launchpad.net/bzr/+bug/897511 # See https://bugs.launchpad.net/bzr/+bug/897511
@ -1101,13 +1052,13 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
end end
def source_modified_time def source_modified_time
timestamp = Utils.popen_read("bzr", "log", "-l", "1", "--timezone=utc", cached_location.to_s)[/^timestamp: (.+)$/, 1] timestamp = Utils.popen_read(bzrpath, "log", "-l", "1", "--timezone=utc", cached_location.to_s)[/^timestamp: (.+)$/, 1]
raise "Could not get any timestamps from bzr!" if timestamp.to_s.empty? raise "Could not get any timestamps from bzr!" if timestamp.to_s.empty?
Time.parse timestamp Time.parse timestamp
end end
def last_commit def last_commit
Utils.popen_read("bzr", "revno", cached_location.to_s).chomp Utils.popen_read(bzrpath, "revno", cached_location.to_s).chomp
end end
private private
@ -1126,7 +1077,9 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
end end
def update def update
cached_location.cd { quiet_safe_system bzrpath, "update" } cached_location.cd do
safe_system bzrpath, "update"
end
end end
end end
@ -1136,6 +1089,10 @@ class FossilDownloadStrategy < VCSDownloadStrategy
@url = @url.sub(%r{^fossil://}, "") @url = @url.sub(%r{^fossil://}, "")
end end
def fossilpath
@fossilpath ||= which("fossil", PATH.new(Formula["fossil"].opt_bin, ENV["PATH"]))
end
def stage def stage
super super
args = [fossilpath, "open", cached_location] args = [fossilpath, "open", cached_location]
@ -1144,11 +1101,11 @@ class FossilDownloadStrategy < VCSDownloadStrategy
end end
def source_modified_time def source_modified_time
Time.parse Utils.popen_read("fossil", "info", "tip", "-R", cached_location.to_s)[/^uuid: +\h+ (.+)$/, 1] Time.parse Utils.popen_read(fossilpath, "info", "tip", "-R", cached_location.to_s)[/^uuid: +\h+ (.+)$/, 1]
end end
def last_commit def last_commit
Utils.popen_read("fossil", "info", "tip", "-R", cached_location.to_s)[/^uuid: +(\h+) .+$/, 1] Utils.popen_read(fossilpath, "info", "tip", "-R", cached_location.to_s)[/^uuid: +(\h+) .+$/, 1]
end end
private private

View File

@ -147,7 +147,7 @@ module SharedEnvExtension
# Outputs the current compiler. # Outputs the current compiler.
# @return [Symbol] # @return [Symbol]
# <pre># Do something only for clang # <pre># Do something only for the system clang
# if ENV.compiler == :clang # if ENV.compiler == :clang
# # modify CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS in one go: # # modify CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS in one go:
# ENV.append_to_cflags "-I ./missing/includes" # ENV.append_to_cflags "-I ./missing/includes"
@ -301,6 +301,18 @@ module SharedEnvExtension
# A no-op until we enable this by default again (which we may never do). # A no-op until we enable this by default again (which we may never do).
def permit_weak_imports; end def permit_weak_imports; end
# @private
def compiler_any_clang?(cc = compiler)
%w[clang llvm_clang].include?(cc.to_s)
end
# @private
def compiler_with_cxx11_support?(cc)
return if compiler_any_clang?(cc)
version = cc[/^gcc-(\d+(?:\.\d+)?)$/, 1]
version && Version.create(version) >= Version.create("4.8")
end
private private
def cc=(val) def cc=(val)
@ -330,11 +342,6 @@ module SharedEnvExtension
return unless homebrew_cc =~ GNU_GCC_REGEXP return unless homebrew_cc =~ GNU_GCC_REGEXP
raise "Non-Apple GCC can't build universal binaries" raise "Non-Apple GCC can't build universal binaries"
end end
def gcc_with_cxx11_support?(cc)
version = cc[/^gcc-(\d+(?:\.\d+)?)$/, 1]
version && Version.create(version) >= Version.create("4.8")
end
end end
require "extend/os/extend/ENV/shared" require "extend/os/extend/ENV/shared"

View File

@ -157,7 +157,7 @@ module Stdenv
append_to_cflags Hardware::CPU.universal_archs.as_arch_flags append_to_cflags Hardware::CPU.universal_archs.as_arch_flags
append "LDFLAGS", Hardware::CPU.universal_archs.as_arch_flags append "LDFLAGS", Hardware::CPU.universal_archs.as_arch_flags
return if compiler == :clang return if compiler_any_clang?
return unless Hardware.is_32_bit? return unless Hardware.is_32_bit?
# Can't mix "-march" for a 32-bit CPU with "-arch x86_64" # 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") replace_in_cflags(/-march=\S*/, "-Xarch_#{Hardware::CPU.arch_32_bit} \\0")
@ -167,7 +167,7 @@ module Stdenv
if compiler == :clang if compiler == :clang
append "CXX", "-std=c++11" append "CXX", "-std=c++11"
append "CXX", "-stdlib=libc++" append "CXX", "-stdlib=libc++"
elsif gcc_with_cxx11_support?(compiler) elsif compiler_with_cxx11_support?(compiler)
append "CXX", "-std=c++11" append "CXX", "-std=c++11"
else else
raise "The selected compiler doesn't support C++11: #{compiler}" raise "The selected compiler doesn't support C++11: #{compiler}"

View File

@ -276,7 +276,7 @@ module Superenv
self["HOMEBREW_ARCHFLAGS"] = Hardware::CPU.universal_archs.as_arch_flags self["HOMEBREW_ARCHFLAGS"] = Hardware::CPU.universal_archs.as_arch_flags
# GCC doesn't accept "-march" for a 32-bit CPU with "-arch x86_64" # GCC doesn't accept "-march" for a 32-bit CPU with "-arch x86_64"
return if compiler == :clang return if compiler_any_clang?
return unless Hardware::CPU.is_32_bit? return unless Hardware::CPU.is_32_bit?
self["HOMEBREW_OPTFLAGS"] = self["HOMEBREW_OPTFLAGS"].sub( self["HOMEBREW_OPTFLAGS"] = self["HOMEBREW_OPTFLAGS"].sub(
/-march=\S*/, /-march=\S*/,
@ -300,7 +300,7 @@ module Superenv
if homebrew_cc == "clang" if homebrew_cc == "clang"
append "HOMEBREW_CCCFG", "x", "" append "HOMEBREW_CCCFG", "x", ""
append "HOMEBREW_CCCFG", "g", "" append "HOMEBREW_CCCFG", "g", ""
elsif gcc_with_cxx11_support?(homebrew_cc) elsif compiler_with_cxx11_support?(homebrew_cc)
append "HOMEBREW_CCCFG", "x", "" append "HOMEBREW_CCCFG", "x", ""
else else
raise "The selected compiler doesn't support C++11: #{homebrew_cc}" raise "The selected compiler doesn't support C++11: #{homebrew_cc}"

View File

@ -436,8 +436,12 @@ class FormulaInstaller
def expand_requirements def expand_requirements
unsatisfied_reqs = Hash.new { |h, k| h[k] = [] } unsatisfied_reqs = Hash.new { |h, k| h[k] = [] }
deps = [] req_deps = []
formulae = [formula] formulae = [formula]
formula_deps_map = Dependency.expand(formula)
.each_with_object({}) do |dep, hash|
hash[dep.name] = dep
end
while f = formulae.pop while f = formulae.pop
runtime_requirements = runtime_requirements(f) runtime_requirements = runtime_requirements(f)
@ -453,6 +457,8 @@ class FormulaInstaller
next next
elsif !runtime_requirements.include?(req) && install_bottle_for_dependent elsif !runtime_requirements.include?(req) && install_bottle_for_dependent
Requirement.prune Requirement.prune
elsif (dep = formula_deps_map[dependent.name]) && dep.build?
Requirement.prune
else else
unsatisfied_reqs[dependent] << req unsatisfied_reqs[dependent] << req
end end
@ -460,9 +466,9 @@ class FormulaInstaller
end end
# Merge the repeated dependencies, which may have different tags. # Merge the repeated dependencies, which may have different tags.
deps = Dependency.merge_repeats(deps) req_deps = Dependency.merge_repeats(req_deps)
[unsatisfied_reqs, deps] [unsatisfied_reqs, req_deps]
end end
def expand_dependencies(deps) def expand_dependencies(deps)

View File

@ -112,10 +112,10 @@ module Formulary
resource = Resource.new(formula_name) { url bottle_name } resource = Resource.new(formula_name) { url bottle_name }
resource.specs[:bottle] = true resource.specs[:bottle] = true
downloader = CurlDownloadStrategy.new resource.name, resource downloader = CurlDownloadStrategy.new resource.name, resource
@bottle_filename = downloader.cached_location cached = downloader.cached_location.exist?
cached = @bottle_filename.exist?
downloader.fetch downloader.fetch
ohai "Pouring the cached bottle" if cached ohai "Pouring the cached bottle" if cached
@bottle_filename = downloader.cached_location
else else
@bottle_filename = Pathname(bottle_name).realpath @bottle_filename = Pathname(bottle_name).realpath
end end

View File

@ -27,12 +27,14 @@ module Homebrew
def check_development_tools def check_development_tools
checks = Diagnostic::Checks.new checks = Diagnostic::Checks.new
failed = false
checks.fatal_development_tools_checks.each do |check| checks.fatal_development_tools_checks.each do |check|
out = checks.send(check) out = checks.send(check)
next if out.nil? next if out.nil?
failed ||= true
ofail out ofail out
end end
exit 1 if Homebrew.failed? exit 1 if failed
end end
def check_cellar def check_cellar

View File

@ -118,7 +118,7 @@ class Resource
if block_given? if block_given?
yield ResourceStageContext.new(self, staging) yield ResourceStageContext.new(self, staging)
elsif target elsif target
target = Pathname.new(target) unless target.is_a? Pathname target = Pathname(target)
target.install Pathname.pwd.children target.install Pathname.pwd.children
end end
end end

View File

@ -1,4 +1,3 @@
require_relative "./rubocops/bottle_block_cop"
require_relative "./rubocops/formula_desc_cop" require_relative "./rubocops/formula_desc_cop"
require_relative "./rubocops/components_order_cop" require_relative "./rubocops/components_order_cop"
require_relative "./rubocops/components_redundancy_cop" require_relative "./rubocops/components_redundancy_cop"

View File

@ -1,29 +0,0 @@
require_relative "./extend/formula_cop"
module RuboCop
module Cop
module FormulaAuditStrict
# This cop audits `bottle` block in Formulae
#
# - `rebuild` should be used instead of `revision` in `bottle` block
class BottleBlock < FormulaCop
MSG = "Use rebuild instead of revision in bottle block".freeze
def audit_formula(_node, _class_node, _parent_class_node, body_node)
bottle = find_block(body_node, :bottle)
return if bottle.nil? || block_size(bottle).zero?
problem "Use rebuild instead of revision in bottle block" if method_called_in_block?(bottle, :revision)
end
def autocorrect(node)
lambda do |corrector|
correction = node.source.sub("revision", "rebuild")
corrector.insert_before(node.source_range, correction)
corrector.remove(node.source_range)
end
end
end
end
end
end

View File

@ -8,10 +8,12 @@ module RuboCop
# - `url|checksum|mirror` should be inside `stable` block # - `url|checksum|mirror` should be inside `stable` block
# - `head` and `head do` should not be simultaneously present # - `head` and `head do` should not be simultaneously present
# - `bottle :unneeded/:disable` and `bottle do` should not be simultaneously present # - `bottle :unneeded/:disable` and `bottle do` should not be simultaneously present
# - `stable do` should not be present without a `head` or `devel` spec
class ComponentsRedundancy < FormulaCop class ComponentsRedundancy < FormulaCop
HEAD_MSG = "`head` and `head do` should not be simultaneously present".freeze HEAD_MSG = "`head` and `head do` should not be simultaneously present".freeze
BOTTLE_MSG = "`bottle :modifier` and `bottle do` should not be simultaneously present".freeze BOTTLE_MSG = "`bottle :modifier` and `bottle do` should not be simultaneously present".freeze
STABLE_MSG = "`stable do` should not be present without a `head` or `devel` spec".freeze
def audit_formula(_node, _class_node, _parent_class_node, body_node) def audit_formula(_node, _class_node, _parent_class_node, body_node)
stable_block = find_block(body_node, :stable) stable_block = find_block(body_node, :stable)
@ -26,6 +28,11 @@ module RuboCop
problem BOTTLE_MSG if method_called?(body_node, :bottle) && problem BOTTLE_MSG if method_called?(body_node, :bottle) &&
find_block(body_node, :bottle) find_block(body_node, :bottle)
return if method_called?(body_node, :head) ||
find_block(body_node, :head) ||
find_block(body_node, :devel)
problem STABLE_MSG if stable_block
end end
end end
end end

View File

@ -50,7 +50,6 @@ module RuboCop
OPTION = "Formulae should not have an `option`".freeze OPTION = "Formulae should not have an `option`".freeze
def audit_formula(_node, _class_node, _parent_class_node, body_node) def audit_formula(_node, _class_node, _parent_class_node, body_node)
return if versioned_formula?
problem DEP_OPTION if method_called_ever?(body_node, :deprecated_option) problem DEP_OPTION if method_called_ever?(body_node, :deprecated_option)
return unless formula_tap == "homebrew-core" return unless formula_tap == "homebrew-core"
problem OPTION if method_called_ever?(body_node, :option) problem OPTION if method_called_ever?(body_node, :option)

View File

@ -116,6 +116,7 @@ class SystemConfig
HOMEBREW_CELLAR: "/usr/local/Cellar", HOMEBREW_CELLAR: "/usr/local/Cellar",
HOMEBREW_CACHE: "#{ENV["HOME"]}/Library/Caches/Homebrew", HOMEBREW_CACHE: "#{ENV["HOME"]}/Library/Caches/Homebrew",
HOMEBREW_RUBY_WARNINGS: "-W0", HOMEBREW_RUBY_WARNINGS: "-W0",
HOMEBREW_TEMP: ENV["HOMEBREW_SYSTEM_TEMP"],
}.freeze }.freeze
boring_keys = %w[ boring_keys = %w[
HOMEBREW_BROWSER HOMEBREW_BROWSER
@ -134,6 +135,7 @@ class SystemConfig
HOMEBREW_MACOS_VERSION HOMEBREW_MACOS_VERSION
HOMEBREW_RUBY_PATH HOMEBREW_RUBY_PATH
HOMEBREW_SYSTEM HOMEBREW_SYSTEM
HOMEBREW_SYSTEM_TEMP
HOMEBREW_OS_VERSION HOMEBREW_OS_VERSION
HOMEBREW_PATH HOMEBREW_PATH
HOMEBREW_PROCESSOR HOMEBREW_PROCESSOR
@ -155,6 +157,9 @@ class SystemConfig
if defaults_hash[:HOMEBREW_RUBY_WARNINGS] != ENV["HOMEBREW_RUBY_WARNINGS"].to_s if defaults_hash[:HOMEBREW_RUBY_WARNINGS] != ENV["HOMEBREW_RUBY_WARNINGS"].to_s
f.puts "HOMEBREW_RUBY_WARNINGS: #{ENV["HOMEBREW_RUBY_WARNINGS"]}" f.puts "HOMEBREW_RUBY_WARNINGS: #{ENV["HOMEBREW_RUBY_WARNINGS"]}"
end end
if defaults_hash[:HOMEBREW_TEMP] != HOMEBREW_TEMP.to_s
f.puts "HOMEBREW_TEMP: #{HOMEBREW_TEMP}"
end
unless ENV["HOMEBREW_ENV"] unless ENV["HOMEBREW_ENV"]
ENV.sort.each do |key, value| ENV.sort.each do |key, value|
next unless key.start_with?("HOMEBREW_") next unless key.start_with?("HOMEBREW_")

View File

@ -284,8 +284,8 @@ class Tap
link_completions_and_manpages link_completions_and_manpages
formula_count = formula_files.size formatted_contents = Formatter.comma_and(*contents)&.prepend(" ")
puts "Tapped #{Formatter.pluralize(formula_count, "formula")} (#{path.abv})" unless quiet puts "Tapped#{formatted_contents} (#{path.abv})." unless quiet
Descriptions.cache_formulae(formula_names) Descriptions.cache_formulae(formula_names)
return if options[:clone_target] return if options[:clone_target]
@ -311,15 +311,18 @@ class Tap
require "descriptions" require "descriptions"
raise TapUnavailableError, name unless installed? raise TapUnavailableError, name unless installed?
puts "Untapping #{name}... (#{path.abv})" puts "Untapping #{name}..."
abv = path.abv
formatted_contents = Formatter.comma_and(*contents)&.prepend(" ")
unpin if pinned? unpin if pinned?
formula_count = formula_files.size
Descriptions.uncache_formulae(formula_names) Descriptions.uncache_formulae(formula_names)
Utils::Link.unlink_manpages(path) Utils::Link.unlink_manpages(path)
Utils::Link.unlink_completions(path) Utils::Link.unlink_completions(path)
path.rmtree path.rmtree
path.parent.rmdir_if_possible path.parent.rmdir_if_possible
puts "Untapped #{Formatter.pluralize(formula_count, "formula")}" puts "Untapped#{formatted_contents} (#{abv})."
clear_cache clear_cache
end end
@ -343,6 +346,24 @@ class Tap
@cask_dir ||= path/"Casks" @cask_dir ||= path/"Casks"
end end
def contents
contents = []
if (command_count = command_files.count).positive?
contents << Formatter.pluralize(command_count, "command")
end
if (cask_count = cask_files.count).positive?
contents << Formatter.pluralize(cask_count, "cask")
end
if (formula_count = formula_files.count).positive?
contents << Formatter.pluralize(formula_count, "formula")
end
contents
end
# an array of all {Formula} files of this {Tap}. # an array of all {Formula} files of this {Tap}.
def formula_files def formula_files
@formula_files ||= if formula_dir.directory? @formula_files ||= if formula_dir.directory?
@ -427,7 +448,8 @@ class Tap
# an array of all commands files of this {Tap}. # an array of all commands files of this {Tap}.
def command_files def command_files
@command_files ||= Pathname.glob("#{path}/cmd/brew-*").select(&:executable?) @command_files ||= Pathname.glob("#{path}/cmd/brew{,cask}-*")
.select { |file| file.executable? || file.extname == ".rb" }
end end
# path to the pin record for this {Tap}. # path to the pin record for this {Tap}.

View File

@ -156,6 +156,18 @@ shared_examples EnvActivation do
expect(subject["FOO"]).to eq "bar" expect(subject["FOO"]).to eq "bar"
end end
end end
describe "#compiler_any_clang?" do
it "returns true for llvm_clang" do
expect(subject.compiler_any_clang?(:llvm_clang)).to be true
end
end
describe "#compiler_with_cxx11_support?" do
it "returns true for gcc-4.9" do
expect(subject.compiler_with_cxx11_support?("gcc-4.9")).to be true
end
end
end end
describe Stdenv do describe Stdenv do

View File

@ -15,7 +15,7 @@ GEM
parallel parallel
parser (2.5.1.0) parser (2.5.1.0)
ast (~> 2.4.0) ast (~> 2.4.0)
powerpack (0.1.1) powerpack (0.1.2)
rainbow (3.0.0) rainbow (3.0.0)
rspec (3.7.0) rspec (3.7.0)
rspec-core (~> 3.7.0) rspec-core (~> 3.7.0)
@ -37,7 +37,7 @@ GEM
rspec-support (3.7.1) rspec-support (3.7.1)
rspec-wait (0.0.9) rspec-wait (0.0.9)
rspec (>= 3, < 4) rspec (>= 3, < 4)
rubocop (0.57.1) rubocop (0.57.2)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.5) parser (>= 2.5)
@ -51,7 +51,7 @@ GEM
json (>= 1.8, < 3) json (>= 1.8, < 3)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.2) simplecov-html (0.10.2)
unicode-display_width (1.3.0) unicode-display_width (1.4.0)
url (0.3.2) url (0.3.2)
PLATFORMS PLATFORMS
@ -64,8 +64,8 @@ DEPENDENCIES
rspec-its rspec-its
rspec-retry rspec-retry
rspec-wait rspec-wait
rubocop (= 0.57.1) rubocop (= 0.57.2)
simplecov simplecov
BUNDLED WITH BUNDLED WITH
1.16.1 1.16.2

View File

@ -333,10 +333,10 @@ describe Hbc::Audit, :cask do
end end
end end
describe "GitHub releases appcast check" do describe "hosting with appcast checks" do
let(:appcast_warning) { /Download uses GitHub releases/ } let(:appcast_warning) { /please add an appcast/ }
context "when the download does not use GitHub releases" do context "when the download does not use hosting with an appcast" do
let(:cask_token) { "basic-cask" } let(:cask_token) { "basic-cask" }
it { is_expected.not_to warn_with(appcast_warning) } it { is_expected.not_to warn_with(appcast_warning) }
@ -353,16 +353,6 @@ describe Hbc::Audit, :cask do
it { is_expected.to warn_with(appcast_warning) } it { is_expected.to warn_with(appcast_warning) }
end end
end
describe "SourceForge appcast check" do
let(:appcast_warning) { /Download is hosted on SourceForge/ }
context "when the download is not hosted on SourceForge" do
let(:cask_token) { "basic-cask" }
it { is_expected.not_to warn_with(appcast_warning) }
end
context "when the download is hosted on SourceForge and has an appcast" do context "when the download is hosted on SourceForge and has an appcast" do
let(:cask_token) { "sourceforge-with-appcast" } let(:cask_token) { "sourceforge-with-appcast" }
@ -375,6 +365,30 @@ describe Hbc::Audit, :cask do
it { is_expected.to warn_with(appcast_warning) } it { is_expected.to warn_with(appcast_warning) }
end end
context "when the download is hosted on DevMate and has an appcast" do
let(:cask_token) { "devmate-with-appcast" }
it { is_expected.not_to warn_with(appcast_warning) }
end
context "when the download is hosted on DevMate and does not have an appcast" do
let(:cask_token) { "devmate-without-appcast" }
it { is_expected.to warn_with(appcast_warning) }
end
context "when the download is hosted on HockeyApp and has an appcast" do
let(:cask_token) { "hockeyapp-with-appcast" }
it { is_expected.not_to warn_with(appcast_warning) }
end
context "when the download is hosted on HockeyApp and does not have an appcast" do
let(:cask_token) { "hockeyapp-without-appcast" }
it { is_expected.to warn_with(appcast_warning) }
end
end end
describe "latest with appcast checks" do describe "latest with appcast checks" do

View File

@ -80,7 +80,7 @@ describe Hbc::SystemCommand, :cask do
options.merge!(print_stdout: true) options.merge!(print_stdout: true)
end end
it "echoes both STDOUT and STDERR", :focus do it "echoes both STDOUT and STDERR" do
expect { described_class.run(command, options) } expect { described_class.run(command, options) }
.to output("1\n3\n5\n").to_stdout .to output("1\n3\n5\n").to_stdout
.and output("2\n4\n6\n").to_stderr .and output("2\n4\n6\n").to_stderr

View File

@ -76,4 +76,22 @@ describe Formatter do
expect(described_class.pluralize(2, "new formula")).to eq("2 new formulae") expect(described_class.pluralize(2, "new formula")).to eq("2 new formulae")
end end
end end
describe "::comma_and" do
it "returns nil if given no arguments" do
expect(described_class.comma_and).to be nil
end
it "returns the input as string if there is only one argument" do
expect(described_class.comma_and(1)).to eq("1")
end
it "concatenates two items with “and”" do
expect(described_class.comma_and(1, 2)).to eq("1 and 2")
end
it "concatenates all items with a comma and appends the last with “and”" do
expect(described_class.comma_and(1, 2, 3)).to eq("1, 2 and 3")
end
end
end end

View File

@ -1,47 +0,0 @@
require_relative "../../rubocops/bottle_block_cop"
describe RuboCop::Cop::FormulaAuditStrict::BottleBlock do
subject(:cop) { described_class.new }
context "When auditing Bottle Block" do
it "When there is revision in bottle block" do
expect_offense(<<~RUBY)
class Foo < Formula
url 'http://example.com/foo-1.0.tgz'
bottle do
cellar :any
revision 2
^^^^^^^^^^ Use rebuild instead of revision in bottle block
end
end
RUBY
end
end
context "When auditing Bottle Block with auto correct" do
it "When there is revision in bottle block" do
source = <<~EOS
class Foo < Formula
url 'http://example.com/foo-1.0.tgz'
bottle do
cellar :any
revision 2
end
end
EOS
corrected_source = <<~EOS
class Foo < Formula
url 'http://example.com/foo-1.0.tgz'
bottle do
cellar :any
rebuild 2
end
end
EOS
new_source = autocorrect_source(source)
expect(new_source).to eq(corrected_source)
end
end
end

View File

@ -12,6 +12,10 @@ describe RuboCop::Cop::FormulaAudit::ComponentsRedundancy do
stable do stable do
# stuff # stuff
end end
devel do
# stuff
end
end end
RUBY RUBY
end end
@ -40,5 +44,45 @@ describe RuboCop::Cop::FormulaAudit::ComponentsRedundancy do
end end
RUBY RUBY
end end
it "When `stable do` is present with a `head` method" do
expect_no_offenses(<<~RUBY)
class Foo < Formula
head "http://example.com/foo.git"
stable do
# stuff
end
end
RUBY
end
it "When `stable do` is present with a `head do` block" do
expect_no_offenses(<<~RUBY)
class Foo < Formula
stable do
# stuff
end
head do
# stuff
end
end
RUBY
end
it "When `stable do` is present with a `devel` block" do
expect_no_offenses(<<~RUBY)
class Foo < Formula
stable do
# stuff
end
devel do
# stuff
end
end
RUBY
end
end end
end end

View File

@ -0,0 +1,12 @@
cask 'devmate-with-appcast' do
version '1.0'
sha256 'a69e7357bea014f4c14ac9699274f559086844ffa46563c4619bf1addfd72ad9'
# dl.devmate.com/com.my.fancyapp was verified as official when first introduced to the cask
url "https://dl.devmate.com/com.my.fancyapp/app_#{version}.zip"
appcast 'https://updates.devmate.com/com.my.fancyapp.app.xml'
name 'DevMate'
homepage 'http://www.example.com/'
app 'DevMate.app'
end

View File

@ -0,0 +1,11 @@
cask 'devmate-without-appcast' do
version '1.0'
sha256 'a69e7357bea014f4c14ac9699274f559086844ffa46563c4619bf1addfd72ad9'
# dl.devmate.com/com.my.fancyapp was verified as official when first introduced to the cask
url "https://dl.devmate.com/com.my.fancyapp/app_#{version}.zip"
name 'DevMate'
homepage 'http://www.example.com/'
app 'DevMate.app'
end

View File

@ -0,0 +1,12 @@
cask 'hockeyapp-with-appcast' do
version '1.0,123'
sha256 'a69e7357bea014f4c14ac9699274f559086844ffa46563c4619bf1addfd72ad9'
# rink.hockeyapp.net/api/2/apps/67503a7926431872c4b6c1549f5bd6b1 was verified as official when first introduced to the cask
url "https://rink.hockeyapp.net/api/2/apps/67503a7926431872c4b6c1549f5bd6b1/app_versions/#{version.after_comma}?format=zip"
appcast 'https://rink.hockeyapp.net/api/2/apps/67503a7926431872c4b6c1549f5bd6b1'
name 'HockeyApp'
homepage 'http://www.example.com/'
app 'HockeyApp.app'
end

View File

@ -0,0 +1,11 @@
cask 'hockeyapp-without-appcast' do
version '1.0,123'
sha256 'a69e7357bea014f4c14ac9699274f559086844ffa46563c4619bf1addfd72ad9'
# rink.hockeyapp.net/api/2/apps/67503a7926431872c4b6c1549f5bd6b1 was verified as official when first introduced to the cask
url "https://rink.hockeyapp.net/api/2/apps/67503a7926431872c4b6c1549f5bd6b1/app_versions/#{version.after_comma}?format=zip"
name 'HockeyApp'
homepage 'http://www.example.com/'
app 'HockeyApp.app'
end

View File

@ -14,27 +14,27 @@ end
# Paths pointing into the Homebrew code base that persist across test runs # Paths pointing into the Homebrew code base that persist across test runs
HOMEBREW_LIBRARY_PATH = Pathname.new(File.expand_path("../../..", __dir__)) HOMEBREW_LIBRARY_PATH = Pathname.new(File.expand_path("../../..", __dir__))
HOMEBREW_SHIMS_PATH = HOMEBREW_LIBRARY_PATH.parent+"Homebrew/shims" HOMEBREW_SHIMS_PATH = HOMEBREW_LIBRARY_PATH.parent/"Homebrew/shims"
HOMEBREW_LOAD_PATH = [ HOMEBREW_LOAD_PATH = [
File.expand_path(__dir__), File.expand_path(__dir__),
HOMEBREW_LIBRARY_PATH, HOMEBREW_LIBRARY_PATH,
HOMEBREW_LIBRARY_PATH.join("cask/lib"), HOMEBREW_LIBRARY_PATH/"cask/lib",
].join(File::PATH_SEPARATOR) ].join(File::PATH_SEPARATOR)
# Paths redirected to a temporary directory and wiped at the end of the test run # Paths redirected to a temporary directory and wiped at the end of the test run
HOMEBREW_PREFIX = Pathname.new(TEST_TMPDIR).join("prefix") HOMEBREW_PREFIX = Pathname(TEST_TMPDIR)/"prefix"
HOMEBREW_REPOSITORY = HOMEBREW_PREFIX HOMEBREW_REPOSITORY = HOMEBREW_PREFIX
HOMEBREW_LIBRARY = HOMEBREW_REPOSITORY+"Library" HOMEBREW_LIBRARY = HOMEBREW_REPOSITORY/"Library"
HOMEBREW_CACHE = HOMEBREW_PREFIX.parent+"cache" HOMEBREW_CACHE = HOMEBREW_PREFIX.parent/"cache"
HOMEBREW_CACHE_FORMULA = HOMEBREW_PREFIX.parent+"formula_cache" HOMEBREW_CACHE_FORMULA = HOMEBREW_PREFIX.parent/"formula_cache"
HOMEBREW_LINKED_KEGS = HOMEBREW_PREFIX.parent+"linked" HOMEBREW_LINKED_KEGS = HOMEBREW_PREFIX.parent/"linked"
HOMEBREW_PINNED_KEGS = HOMEBREW_PREFIX.parent+"pinned" HOMEBREW_PINNED_KEGS = HOMEBREW_PREFIX.parent/"pinned"
HOMEBREW_LOCK_DIR = HOMEBREW_PREFIX.parent+"locks" HOMEBREW_LOCK_DIR = HOMEBREW_PREFIX.parent/"locks"
HOMEBREW_CELLAR = HOMEBREW_PREFIX.parent+"cellar" HOMEBREW_CELLAR = HOMEBREW_PREFIX.parent/"cellar"
HOMEBREW_LOGS = HOMEBREW_PREFIX.parent+"logs" HOMEBREW_LOGS = HOMEBREW_PREFIX.parent/"logs"
HOMEBREW_TEMP = HOMEBREW_PREFIX.parent+"temp" HOMEBREW_TEMP = HOMEBREW_PREFIX.parent/"temp"
TEST_FIXTURE_DIR = HOMEBREW_LIBRARY_PATH.join("test", "support", "fixtures") TEST_FIXTURE_DIR = HOMEBREW_LIBRARY_PATH/"test/support/fixtures"
TESTBALL_SHA256 = "91e3f7930c98d7ccfb288e115ed52d06b0e5bc16fec7dce8bdda86530027067b".freeze TESTBALL_SHA256 = "91e3f7930c98d7ccfb288e115ed52d06b0e5bc16fec7dce8bdda86530027067b".freeze
TESTBALL_PATCHES_SHA256 = "799c2d551ac5c3a5759bea7796631a7906a6a24435b52261a317133a0bfb34d9".freeze TESTBALL_PATCHES_SHA256 = "799c2d551ac5c3a5759bea7796631a7906a6a24435b52261a317133a0bfb34d9".freeze

View File

@ -108,4 +108,14 @@ module Formatter
show_count ? "#{count} #{words}" : words show_count ? "#{count} #{words}" : words
end end
def comma_and(*items)
# TODO: Remove when RuboCop 0.57.3 is released.
# False positive has been fixed and merged, but is not yet in a
# stable release: https://github.com/rubocop-hq/rubocop/pull/6038
*items, last = items.map(&:to_s) # rubocop:disable Lint/ShadowedArgument
return last if items.empty?
"#{items.join(", ")} and #{last}"
end
end end

View File

@ -3,7 +3,7 @@ Vendored Dependencies
* [plist](https://github.com/patsplat/plist), version 3.3.0 * [plist](https://github.com/patsplat/plist), version 3.3.0
* [ruby-macho](https://github.com/Homebrew/ruby-macho), version 1.2.0 * [ruby-macho](https://github.com/Homebrew/ruby-macho), version 2.0.0
* [backports](https://github.com/marcandre/backports), version 3.8.0 * [backports](https://github.com/marcandre/backports), version 3.8.0

View File

@ -1,18 +1,18 @@
require "#{File.dirname(__FILE__)}/macho/structure" require_relative "macho/structure"
require "#{File.dirname(__FILE__)}/macho/view" require_relative "macho/view"
require "#{File.dirname(__FILE__)}/macho/headers" require_relative "macho/headers"
require "#{File.dirname(__FILE__)}/macho/load_commands" require_relative "macho/load_commands"
require "#{File.dirname(__FILE__)}/macho/sections" require_relative "macho/sections"
require "#{File.dirname(__FILE__)}/macho/macho_file" require_relative "macho/macho_file"
require "#{File.dirname(__FILE__)}/macho/fat_file" require_relative "macho/fat_file"
require "#{File.dirname(__FILE__)}/macho/exceptions" require_relative "macho/exceptions"
require "#{File.dirname(__FILE__)}/macho/utils" require_relative "macho/utils"
require "#{File.dirname(__FILE__)}/macho/tools" require_relative "macho/tools"
# The primary namespace for ruby-macho. # The primary namespace for ruby-macho.
module MachO module MachO
# release version # release version
VERSION = "1.2.0".freeze VERSION = "2.0.0".freeze
# Opens the given filename as a MachOFile or FatFile, depending on its magic. # Opens the given filename as a MachOFile or FatFile, depending on its magic.
# @param filename [String] the file being opened # @param filename [String] the file being opened

View File

@ -23,21 +23,37 @@ module MachO
# Creates a new FatFile from the given (single-arch) Mach-Os # Creates a new FatFile from the given (single-arch) Mach-Os
# @param machos [Array<MachOFile>] the machos to combine # @param machos [Array<MachOFile>] the machos to combine
# @return [FatFile] a new FatFile containing the give machos # @return [FatFile] a new FatFile containing the give machos
# @raise [ArgumentError] if less than one Mach-O is given
def self.new_from_machos(*machos) def self.new_from_machos(*machos)
header = Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size) raise ArgumentError, "expected at least one Mach-O" if machos.empty?
# put the smaller alignments further forwards in fat macho, so that we do less padding
machos = machos.sort_by(&:segment_alignment)
bin = +""
bin << Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size).serialize
offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize) offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize)
fat_archs = []
macho_pads = {}
macho_bins = {}
machos.each do |macho| machos.each do |macho|
fat_archs << Headers::FatArch.new(macho.header.cputype, macho_offset = Utils.round(offset, 2**macho.segment_alignment)
macho.header.cpusubtype, macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
offset, macho.serialize.bytesize, macho_bins[macho] = macho.serialize
macho.alignment)
offset += macho.serialize.bytesize bin << Headers::FatArch.new(macho.header.cputype, macho.header.cpusubtype,
macho_offset, macho_bins[macho].bytesize,
macho.segment_alignment).serialize
offset += (macho_bins[macho].bytesize + macho_pads[macho])
end end
bin = header.serialize machos.each do |macho|
bin << fat_archs.map(&:serialize).join bin << Utils.nullpad(macho_pads[macho])
bin << machos.map(&:serialize).join bin << macho_bins[macho]
end
new_from_bin(bin) new_from_bin(bin)
end end
@ -265,6 +281,15 @@ module MachO
File.open(@filename, "wb") { |f| f.write(@raw_data) } File.open(@filename, "wb") { |f| f.write(@raw_data) }
end end
# @return [Hash] a hash representation of this {FatFile}
def to_h
{
"header" => header.to_h,
"fat_archs" => fat_archs.map(&:to_h),
"machos" => machos.map(&:to_h),
}
end
private private
# Obtain the fat header from raw file data. # Obtain the fat header from raw file data.

View File

@ -475,6 +475,15 @@ module MachO
def serialize def serialize
[magic, nfat_arch].pack(FORMAT) [magic, nfat_arch].pack(FORMAT)
end end
# @return [Hash] a hash representation of this {FatHeader}
def to_h
{
"magic" => magic,
"magic_sym" => MH_MAGICS[magic],
"nfat_arch" => nfat_arch,
}.merge super
end
end end
# Fat binary header architecture structure. A Fat binary has one or more of # Fat binary header architecture structure. A Fat binary has one or more of
@ -508,7 +517,7 @@ module MachO
# @api private # @api private
def initialize(cputype, cpusubtype, offset, size, align) def initialize(cputype, cpusubtype, offset, size, align)
@cputype = cputype @cputype = cputype
@cpusubtype = cpusubtype @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
@offset = offset @offset = offset
@size = size @size = size
@align = align @align = align
@ -518,6 +527,19 @@ module MachO
def serialize def serialize
[cputype, cpusubtype, offset, size, align].pack(FORMAT) [cputype, cpusubtype, offset, size, align].pack(FORMAT)
end end
# @return [Hash] a hash representation of this {FatArch}
def to_h
{
"cputype" => cputype,
"cputype_sym" => CPU_TYPES[cputype],
"cpusubtype" => cpusubtype,
"cpusubtype_sym" => CPU_SUBTYPES[cputype][cpusubtype],
"offset" => offset,
"size" => size,
"align" => align,
}.merge super
end
end end
# 32-bit Mach-O file header structure # 32-bit Mach-O file header structure
@ -639,6 +661,24 @@ module MachO
def alignment def alignment
magic32? ? 4 : 8 magic32? ? 4 : 8
end end
# @return [Hash] a hash representation of this {MachHeader}
def to_h
{
"magic" => magic,
"magic_sym" => MH_MAGICS[magic],
"cputype" => cputype,
"cputype_sym" => CPU_TYPES[cputype],
"cpusubtype" => cpusubtype,
"cpusubtype_sym" => CPU_SUBTYPES[cputype][cpusubtype],
"filetype" => filetype,
"filetype_sym" => MH_FILETYPES[filetype],
"ncmds" => ncmds,
"sizeofcmds" => sizeofcmds,
"flags" => flags,
"alignment" => alignment,
}.merge super
end
end end
# 64-bit Mach-O file header structure # 64-bit Mach-O file header structure
@ -660,6 +700,13 @@ module MachO
super(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags) super(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
@reserved = reserved @reserved = reserved
end end
# @return [Hash] a hash representation of this {MachHeader64}
def to_h
{
"reserved" => reserved,
}.merge super
end
end end
end end
end end

View File

@ -143,7 +143,7 @@ module MachO
:LC_LINKER_OPTIMIZATION_HINT => "LinkeditDataCommand", :LC_LINKER_OPTIMIZATION_HINT => "LinkeditDataCommand",
:LC_VERSION_MIN_TVOS => "VersionMinCommand", :LC_VERSION_MIN_TVOS => "VersionMinCommand",
:LC_VERSION_MIN_WATCHOS => "VersionMinCommand", :LC_VERSION_MIN_WATCHOS => "VersionMinCommand",
:LC_NOTE => "LoadCommand", :LC_NOTE => "NoteCommand",
:LC_BUILD_VERSION => "BuildVersionCommand", :LC_BUILD_VERSION => "BuildVersionCommand",
}.freeze }.freeze
@ -169,15 +169,16 @@ module MachO
:SG_PROTECTED_VERSION_1 => 0x8, :SG_PROTECTED_VERSION_1 => 0x8,
}.freeze }.freeze
# Mach-O load command structure # The top-level Mach-O load command structure.
# This is the most generic load command - only cmd ID and size are #
# represented, and no actual data. Used when a more specific class # This is the most generic load command -- only the type ID and size are
# isn't available/implemented. # represented. Used when a more specific class isn't available or isn't implemented.
class LoadCommand < MachOStructure class LoadCommand < MachOStructure
# @return [MachO::MachOView] the raw view associated with the load command # @return [MachO::MachOView, nil] the raw view associated with the load command,
# or nil if the load command was created via {create}.
attr_reader :view attr_reader :view
# @return [Integer] the load command's identifying number # @return [Integer] the load command's type ID
attr_reader :cmd attr_reader :cmd
# @return [Integer] the size of the load command, in bytes # @return [Integer] the size of the load command, in bytes
@ -251,8 +252,8 @@ module MachO
view.offset view.offset
end end
# @return [Symbol] a symbol representation of the load command's # @return [Symbol, nil] a symbol representation of the load command's
# identifying number # type ID, or nil if the ID doesn't correspond to a known load command class
def type def type
LOAD_COMMANDS[cmd] LOAD_COMMANDS[cmd]
end end
@ -265,6 +266,17 @@ module MachO
type.to_s type.to_s
end end
# @return [Hash] a hash representation of this load command
# @note Children should override this to include additional information.
def to_h
{
"view" => view.to_h,
"cmd" => cmd,
"cmdsize" => cmdsize,
"type" => type,
}.merge super
end
# Represents a Load Command string. A rough analogue to the lc_str # Represents a Load Command string. A rough analogue to the lc_str
# struct used internally by OS X. This class allows ruby-macho to # struct used internally by OS X. This class allows ruby-macho to
# pretend that strings stored in LCs are immediately available without # pretend that strings stored in LCs are immediately available without
@ -304,6 +316,14 @@ module MachO
def to_i def to_i
@string_offset @string_offset
end end
# @return [Hash] a hash representation of this {LCStr}.
def to_h
{
"string" => to_s,
"offset" => to_i,
}
end
end end
# Represents the contextual information needed by a load command to # Represents the contextual information needed by a load command to
@ -364,6 +384,14 @@ module MachO
segs.join("-") segs.join("-")
end end
# @return [Hash] returns a hash representation of this {UUIDCommand}
def to_h
{
"uuid" => uuid,
"uuid_string" => uuid_string,
}.merge super
end
end end
# A load command indicating that part of this file is to be mapped into # A load command indicating that part of this file is to be mapped into
@ -398,7 +426,7 @@ module MachO
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
# @api private # @api private
FORMAT = "L=2a16L=4l=2L=2".freeze FORMAT = "L=2Z16L=4l=2L=2".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
# @api private # @api private
@ -408,7 +436,7 @@ module MachO
def initialize(view, cmd, cmdsize, segname, vmaddr, vmsize, fileoff, def initialize(view, cmd, cmdsize, segname, vmaddr, vmsize, fileoff,
filesize, maxprot, initprot, nsects, flags) filesize, maxprot, initprot, nsects, flags)
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@segname = segname.delete("\x00") @segname = segname
@vmaddr = vmaddr @vmaddr = vmaddr
@vmsize = vmsize @vmsize = vmsize
@fileoff = fileoff @fileoff = fileoff
@ -448,6 +476,42 @@ module MachO
return false if flag.nil? return false if flag.nil?
flags & flag == flag flags & flag == flag
end end
# Guesses the alignment of the segment.
# @return [Integer] the guessed alignment, as a power of 2
# @note See `guess_align` in `cctools/misc/lipo.c`
def guess_align
return Sections::MAX_SECT_ALIGN if vmaddr.zero?
align = 0
segalign = 1
while (segalign & vmaddr).zero?
segalign <<= 1
align += 1
end
return 2 if align < 2
return Sections::MAX_SECT_ALIGN if align > Sections::MAX_SECT_ALIGN
align
end
# @return [Hash] a hash representation of this {SegmentCommand}
def to_h
{
"segname" => segname,
"vmaddr" => vmaddr,
"vmsize" => vmsize,
"fileoff" => fileoff,
"filesize" => filesize,
"maxprot" => maxprot,
"initprot" => initprot,
"nsects" => nsects,
"flags" => flags,
"sections" => sections.map(&:to_h),
}.merge super
end
end end
# A load command indicating that part of this file is to be mapped into # A load command indicating that part of this file is to be mapped into
@ -455,7 +519,7 @@ module MachO
class SegmentCommand64 < SegmentCommand class SegmentCommand64 < SegmentCommand
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
# @api private # @api private
FORMAT = "L=2a16Q=4l=2L=2".freeze FORMAT = "L=2Z16Q=4l=2L=2".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
# @api private # @api private
@ -510,6 +574,16 @@ module MachO
[cmd, cmdsize, string_offsets[:name], timestamp, current_version, [cmd, cmdsize, string_offsets[:name], timestamp, current_version,
compatibility_version].pack(format) + string_payload compatibility_version].pack(format) + string_payload
end end
# @return [Hash] a hash representation of this {DylibCommand}
def to_h
{
"name" => name.to_h,
"timestamp" => timestamp,
"current_version" => current_version,
"compatibility_version" => compatibility_version,
}.merge super
end
end end
# A load command representing some aspect of the dynamic linker, depending # A load command representing some aspect of the dynamic linker, depending
@ -546,6 +620,13 @@ module MachO
cmdsize = SIZEOF + string_payload.bytesize cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload [cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload
end end
# @return [Hash] a hash representation of this {DylinkerCommand}
def to_h
{
"name" => name.to_h,
}.merge super
end
end end
# A load command used to indicate dynamic libraries used in prebinding. # A load command used to indicate dynamic libraries used in prebinding.
@ -576,6 +657,15 @@ module MachO
@nmodules = nmodules @nmodules = nmodules
@linked_modules = linked_modules @linked_modules = linked_modules
end end
# @return [Hash] a hash representation of this {PreboundDylibCommand}
def to_h
{
"name" => name.to_h,
"nmodules" => nmodules,
"linked_modules" => linked_modules,
}.merge super
end
end end
# A load command used to represent threads. # A load command used to represent threads.
@ -641,6 +731,20 @@ module MachO
@reserved5 = reserved5 @reserved5 = reserved5
@reserved6 = reserved6 @reserved6 = reserved6
end end
# @return [Hash] a hash representation of this {RoutinesCommand}
def to_h
{
"init_address" => init_address,
"init_module" => init_module,
"reserved1" => reserved1,
"reserved2" => reserved2,
"reserved3" => reserved3,
"reserved4" => reserved4,
"reserved5" => reserved5,
"reserved6" => reserved6,
}.merge super
end
end end
# A load command containing the address of the dynamic shared library # A load command containing the address of the dynamic shared library
@ -675,6 +779,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@umbrella = LCStr.new(self, umbrella) @umbrella = LCStr.new(self, umbrella)
end end
# @return [Hash] a hash representation of this {SubFrameworkCommand}
def to_h
{
"umbrella" => umbrella.to_h,
}.merge super
end
end end
# A load command signifying membership of a subumbrella containing the name # A load command signifying membership of a subumbrella containing the name
@ -696,6 +807,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@sub_umbrella = LCStr.new(self, sub_umbrella) @sub_umbrella = LCStr.new(self, sub_umbrella)
end end
# @return [Hash] a hash representation of this {SubUmbrellaCommand}
def to_h
{
"sub_umbrella" => sub_umbrella.to_h,
}.merge super
end
end end
# A load command signifying a sublibrary of a shared library. Corresponds # A load command signifying a sublibrary of a shared library. Corresponds
@ -717,6 +835,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@sub_library = LCStr.new(self, sub_library) @sub_library = LCStr.new(self, sub_library)
end end
# @return [Hash] a hash representation of this {SubLibraryCommand}
def to_h
{
"sub_library" => sub_library.to_h,
}.merge super
end
end end
# A load command signifying a shared library that is a subframework of # A load command signifying a shared library that is a subframework of
@ -738,6 +863,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@sub_client = LCStr.new(self, sub_client) @sub_client = LCStr.new(self, sub_client)
end end
# @return [Hash] a hash representation of this {SubClientCommand}
def to_h
{
"sub_client" => sub_client.to_h,
}.merge super
end
end end
# A load command containing the offsets and sizes of the link-edit 4.3BSD # A load command containing the offsets and sizes of the link-edit 4.3BSD
@ -749,10 +881,10 @@ module MachO
# @return [Integer] the number of symbol table entries # @return [Integer] the number of symbol table entries
attr_reader :nsyms attr_reader :nsyms
# @return the string table's offset # @return [Integer] the string table's offset
attr_reader :stroff attr_reader :stroff
# @return the string table size in bytes # @return [Integer] the string table size in bytes
attr_reader :strsize attr_reader :strsize
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -771,6 +903,16 @@ module MachO
@stroff = stroff @stroff = stroff
@strsize = strsize @strsize = strsize
end end
# @return [Hash] a hash representation of this {SymtabCommand}
def to_h
{
"symoff" => symoff,
"nsyms" => nsyms,
"stroff" => stroff,
"strsize" => strsize,
}.merge super
end
end end
# A load command containing symbolic information needed to support data # A load command containing symbolic information needed to support data
@ -864,6 +1006,30 @@ module MachO
@locreloff = locreloff @locreloff = locreloff
@nlocrel = nlocrel @nlocrel = nlocrel
end end
# @return [Hash] a hash representation of this {DysymtabCommand}
def to_h
{
"ilocalsym" => ilocalsym,
"nlocalsym" => nlocalsym,
"iextdefsym" => iextdefsym,
"nextdefsym" => nextdefsym,
"iundefsym" => iundefsym,
"nundefsym" => nundefsym,
"tocoff" => tocoff,
"ntoc" => ntoc,
"modtaboff" => modtaboff,
"nmodtab" => nmodtab,
"extrefsymoff" => extrefsymoff,
"nextrefsyms" => nextrefsyms,
"indirectsymoff" => indirectsymoff,
"nindirectsyms" => nindirectsyms,
"extreloff" => extreloff,
"nextrel" => nextrel,
"locreloff" => locreloff,
"nlocrel" => nlocrel,
}.merge super
end
end end
# A load command containing the offset and number of hints in the two-level # A load command containing the offset and number of hints in the two-level
@ -895,6 +1061,15 @@ module MachO
@table = TwolevelHintsTable.new(view, htoffset, nhints) @table = TwolevelHintsTable.new(view, htoffset, nhints)
end end
# @return [Hash] a hash representation of this {TwolevelHintsCommand}
def to_h
{
"htoffset" => htoffset,
"nhints" => nhints,
"table" => table.hints.map(&:to_h),
}.merge super
end
# A representation of the two-level namespace lookup hints table exposed # A representation of the two-level namespace lookup hints table exposed
# by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`). # by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`).
class TwolevelHintsTable class TwolevelHintsTable
@ -927,6 +1102,14 @@ module MachO
@isub_image = blob >> 24 @isub_image = blob >> 24
@itoc = blob & 0x00FFFFFF @itoc = blob & 0x00FFFFFF
end end
# @return [Hash] a hash representation of this {TwolevelHint}
def to_h
{
"isub_image" => isub_image,
"itoc" => itoc,
}
end
end end
end end
end end
@ -950,6 +1133,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@cksum = cksum @cksum = cksum
end end
# @return [Hash] a hash representation of this {PrebindCksumCommand}
def to_h
{
"cksum" => cksum,
}.merge super
end
end end
# A load command representing an rpath, which specifies a path that should # A load command representing an rpath, which specifies a path that should
@ -984,6 +1174,13 @@ module MachO
cmdsize = SIZEOF + string_payload.bytesize cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload [cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload
end end
# @return [Hash] a hash representation of this {RpathCommand}
def to_h
{
"path" => path.to_h,
}.merge super
end
end end
# A load command representing the offsets and sizes of a blob of data in # A load command representing the offsets and sizes of a blob of data in
@ -1011,6 +1208,14 @@ module MachO
@dataoff = dataoff @dataoff = dataoff
@datasize = datasize @datasize = datasize
end end
# @return [Hash] a hash representation of this {LinkeditDataCommand}
def to_h
{
"dataoff" => dataoff,
"datasize" => datasize,
}.merge super
end
end end
# A load command representing the offset to and size of an encrypted # A load command representing the offset to and size of an encrypted
@ -1040,20 +1245,20 @@ module MachO
@cryptsize = cryptsize @cryptsize = cryptsize
@cryptid = cryptid @cryptid = cryptid
end end
# @return [Hash] a hash representation of this {EncryptionInfoCommand}
def to_h
{
"cryptoff" => cryptoff,
"cryptsize" => cryptsize,
"cryptid" => cryptid,
}.merge super
end
end end
# A load command representing the offset to and size of an encrypted # A load command representing the offset to and size of an encrypted
# segment. Corresponds to LC_ENCRYPTION_INFO_64. # segment. Corresponds to LC_ENCRYPTION_INFO_64.
class EncryptionInfoCommand64 < LoadCommand class EncryptionInfoCommand64 < EncryptionInfoCommand
# @return [Integer] the offset to the encrypted segment
attr_reader :cryptoff
# @return [Integer] the size of the encrypted segment
attr_reader :cryptsize
# @return [Integer] the encryption system, or 0 if not encrypted yet
attr_reader :cryptid
# @return [Integer] 64-bit padding value # @return [Integer] 64-bit padding value
attr_reader :pad attr_reader :pad
@ -1067,12 +1272,16 @@ module MachO
# @api private # @api private
def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad) def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad)
super(view, cmd, cmdsize) super(view, cmd, cmdsize, cryptoff, cryptsize, cryptid)
@cryptoff = cryptoff
@cryptsize = cryptsize
@cryptid = cryptid
@pad = pad @pad = pad
end end
# @return [Hash] a hash representation of this {EncryptionInfoCommand64}
def to_h
{
"pad" => pad,
}.merge super
end
end end
# A load command containing the minimum OS version on which the binary # A load command containing the minimum OS version on which the binary
@ -1121,6 +1330,16 @@ module MachO
segs.join(".") segs.join(".")
end end
# @return [Hash] a hash representation of this {VersionMinCommand}
def to_h
{
"version" => version,
"version_string" => version_string,
"sdk" => sdk,
"sdk_string" => sdk_string,
}.merge super
end
end end
# A load command containing the minimum OS version on which # A load command containing the minimum OS version on which
@ -1156,6 +1375,40 @@ module MachO
@tool_entries = ToolEntries.new(view, ntools) @tool_entries = ToolEntries.new(view, ntools)
end end
# A string representation of the binary's minimum OS version.
# @return [String] a string representing the minimum OS version.
def minos_string
binary = "%032b" % minos
segs = [
binary[0..15], binary[16..23], binary[24..31]
].map { |s| s.to_i(2) }
segs.join(".")
end
# A string representation of the binary's SDK version.
# @return [String] a string representing the SDK version.
def sdk_string
binary = "%032b" % sdk
segs = [
binary[0..15], binary[16..23], binary[24..31]
].map { |s| s.to_i(2) }
segs.join(".")
end
# @return [Hash] a hash representation of this {BuildVersionCommand}
def to_h
{
"platform" => platform,
"minos" => minos,
"minos_string" => minos_string,
"sdk" => sdk,
"sdk_string" => sdk_string,
"tool_entries" => tool_entries.tools.map(&:to_h),
}.merge super
end
# A representation of the tool versions exposed # A representation of the tool versions exposed
# by a {BuildVersionCommand} (`LC_BUILD_VERSION`). # by a {BuildVersionCommand} (`LC_BUILD_VERSION`).
class ToolEntries class ToolEntries
@ -1181,36 +1434,22 @@ module MachO
# @return [Integer] the tool's version number # @return [Integer] the tool's version number
attr_reader :version attr_reader :version
# @param tool 32-bit integer # @param tool [Integer] 32-bit integer
# # @param version 32-bit integer # @param version [Integer] 32-bit integer
# @api private # @api private
def initialize(tool, version) def initialize(tool, version)
@tool = tool @tool = tool
@version = version @version = version
end end
# @return [Hash] a hash representation of this {Tool}
def to_h
{
"tool" => tool,
"version" => version,
}
end end
end end
# A string representation of the binary's minimum OS version.
# @return [String] a string representing the minimum OS version.
def minos_string
binary = "%032b" % minos
segs = [
binary[0..15], binary[16..23], binary[24..31]
].map { |s| s.to_i(2) }
segs.join(".")
end
# A string representation of the binary's SDK version.
# @return [String] a string representing the SDK version.
def sdk_string
binary = "%032b" % sdk
segs = [
binary[0..15], binary[16..23], binary[24..31]
].map { |s| s.to_i(2) }
segs.join(".")
end end
end end
@ -1272,6 +1511,22 @@ module MachO
@export_off = export_off @export_off = export_off
@export_size = export_size @export_size = export_size
end end
# @return [Hash] a hash representation of this {DyldInfoCommand}
def to_h
{
"rebase_off" => rebase_off,
"rebase_size" => rebase_size,
"bind_off" => bind_off,
"bind_size" => bind_size,
"weak_bind_off" => weak_bind_off,
"weak_bind_size" => weak_bind_size,
"lazy_bind_off" => lazy_bind_off,
"lazy_bind_size" => lazy_bind_size,
"export_off" => export_off,
"export_size" => export_size,
}.merge super
end
end end
# A load command containing linker options embedded in object files. # A load command containing linker options embedded in object files.
@ -1293,6 +1548,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@count = count @count = count
end end
# @return [Hash] a hash representation of this {LinkerOptionCommand}
def to_h
{
"count" => count,
}.merge super
end
end end
# A load command specifying the offset of main(). Corresponds to LC_MAIN. # A load command specifying the offset of main(). Corresponds to LC_MAIN.
@ -1317,6 +1579,14 @@ module MachO
@entryoff = entryoff @entryoff = entryoff
@stacksize = stacksize @stacksize = stacksize
end end
# @return [Hash] a hash representation of this {EntryPointCommand}
def to_h
{
"entryoff" => entryoff,
"stacksize" => stacksize,
}.merge super
end
end end
# A load command specifying the version of the sources used to build the # A load command specifying the version of the sources used to build the
@ -1350,6 +1620,14 @@ module MachO
segs.join(".") segs.join(".")
end end
# @return [Hash] a hash representation of this {SourceVersionCommand}
def to_h
{
"version" => version,
"version_string" => version_string,
}.merge super
end
end end
# An obsolete load command containing the offset and size of the (GNU style) # An obsolete load command containing the offset and size of the (GNU style)
@ -1375,6 +1653,14 @@ module MachO
@offset = offset @offset = offset
@size = size @size = size
end end
# @return [Hash] a hash representation of this {SymsegCommand}
def to_h
{
"offset" => offset,
"size" => size,
}.merge super
end
end end
# An obsolete load command containing a free format string table. Each # An obsolete load command containing a free format string table. Each
@ -1412,6 +1698,14 @@ module MachO
@name = LCStr.new(self, name) @name = LCStr.new(self, name)
@header_addr = header_addr @header_addr = header_addr
end end
# @return [Hash] a hash representation of this {FvmfileCommand}
def to_h
{
"name" => name.to_h,
"header_addr" => header_addr,
}.merge super
end
end end
# An obsolete load command containing the path to a library to be loaded # An obsolete load command containing the path to a library to be loaded
@ -1440,6 +1734,52 @@ module MachO
@minor_version = minor_version @minor_version = minor_version
@header_addr = header_addr @header_addr = header_addr
end end
# @return [Hash] a hash representation of this {FvmlibCommand}
def to_h
{
"name" => name.to_h,
"minor_version" => minor_version,
"header_addr" => header_addr,
}.merge super
end
end
# A load command containing an owner name and offset/size for an arbitrary data region.
# Corresponds to LC_NOTE.
class NoteCommand < LoadCommand
# @return [String] the name of the owner for this note
attr_reader :data_owner
# @return [Integer] the offset, within the file, of the note
attr_reader :offset
# @return [Integer] the size, in bytes, of the note
attr_reader :size
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L=2Z16Q=2".freeze
# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 48
def initialize(view, cmd, cmdsize, data_owner, offset, size)
super(view, cmd, cmdsize)
@data_owner = data_owner
@offset = offset
@size = size
end
# @return [Hash] a hash representation of this {NoteCommand}
def to_h
{
"data_owner" => data_owner,
"offset" => offset,
"size" => size,
}.merge super
end
end end
end end
end end

View File

@ -25,7 +25,7 @@ module MachO
# @note load commands are provided in order of ascending offset. # @note load commands are provided in order of ascending offset.
attr_reader :load_commands attr_reader :load_commands
# Creates a new MachOFile instance from a binary string. # Creates a new instance from a binary string.
# @param bin [String] a binary string containing raw Mach-O data # @param bin [String] a binary string containing raw Mach-O data
# @return [MachOFile] a new MachOFile # @return [MachOFile] a new MachOFile
def self.new_from_bin(bin) def self.new_from_bin(bin)
@ -35,7 +35,7 @@ module MachO
instance instance
end end
# Creates a new FatFile from the given filename. # Creates a new instance from data read from the given filename.
# @param filename [String] the Mach-O file to load from # @param filename [String] the Mach-O file to load from
# @raise [ArgumentError] if the given file does not exist # @raise [ArgumentError] if the given file does not exist
def initialize(filename) def initialize(filename)
@ -219,8 +219,7 @@ module MachO
update_sizeofcmds(sizeofcmds - lc.cmdsize) update_sizeofcmds(sizeofcmds - lc.cmdsize)
# pad the space after the load commands to preserve offsets # pad the space after the load commands to preserve offsets
null_pad = "\x00" * lc.cmdsize @raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, Utils.nullpad(lc.cmdsize))
@raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, null_pad)
populate_fields if options.fetch(:repopulate, true) populate_fields if options.fetch(:repopulate, true)
end end
@ -252,6 +251,33 @@ module MachO
end end
end end
# The segment alignment for the Mach-O. Guesses conservatively.
# @return [Integer] the alignment, as a power of 2
# @note This is **not** the same as {#alignment}!
# @note See `get_align` and `get_align_64` in `cctools/misc/lipo.c`
def segment_alignment
# special cases: 12 for x86/64/PPC/PP64, 14 for ARM/ARM64
return 12 if %i[i386 x86_64 ppc ppc64].include?(cputype)
return 14 if %i[arm arm64].include?(cputype)
cur_align = Sections::MAX_SECT_ALIGN
segments.each do |segment|
if filetype == :object
# start with the smallest alignment, and work our way up
align = magic32? ? 2 : 3
segment.sections.each do |section|
align = section.align unless section.align <= align
end
else
align = segment.guess_align
end
cur_align = align if align < cur_align
end
cur_align
end
# The Mach-O's dylib ID, or `nil` if not a dylib. # The Mach-O's dylib ID, or `nil` if not a dylib.
# @example # @example
# file.dylib_id # => 'libBar.dylib' # file.dylib_id # => 'libBar.dylib'
@ -408,6 +434,14 @@ module MachO
File.open(@filename, "wb") { |f| f.write(@raw_data) } File.open(@filename, "wb") { |f| f.write(@raw_data) }
end end
# @return [Hash] a hash representation of this {MachOFile}
def to_h
{
"header" => header.to_h,
"load_commands" => load_commands.map(&:to_h),
}
end
private private
# The file's Mach-O header structure. # The file's Mach-O header structure.

View File

@ -13,6 +13,10 @@ module MachO
# system settable attributes mask # system settable attributes mask
SECTION_ATTRIBUTES_SYS = 0x00ffff00 SECTION_ATTRIBUTES_SYS = 0x00ffff00
# maximum specifiable section alignment, as a power of 2
# @note see `MAXSECTALIGN` macro in `cctools/misc/lipo.c`
MAX_SECT_ALIGN = 15
# association of section flag symbols to values # association of section flag symbols to values
# @api private # @api private
SECTION_FLAGS = { SECTION_FLAGS = {
@ -104,7 +108,7 @@ module MachO
attr_reader :reserved2 attr_reader :reserved2
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
FORMAT = "a16a16L=9".freeze FORMAT = "Z16Z16L=9".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
SIZEOF = 68 SIZEOF = 68
@ -125,16 +129,14 @@ module MachO
@reserved2 = reserved2 @reserved2 = reserved2
end end
# @return [String] the section's name, with any trailing NULL characters # @return [String] the section's name
# removed
def section_name def section_name
sectname.delete("\x00") sectname
end end
# @return [String] the parent segment's name, with any trailing NULL # @return [String] the parent segment's name
# characters removed
def segment_name def segment_name
segname.delete("\x00") segname
end end
# @return [Boolean] whether the section is empty (i.e, {size} is 0) # @return [Boolean] whether the section is empty (i.e, {size} is 0)
@ -151,6 +153,23 @@ module MachO
return false if flag.nil? return false if flag.nil?
flags & flag == flag flags & flag == flag
end end
# @return [Hash] a hash representation of this {Section}
def to_h
{
"sectname" => sectname,
"segname" => segname,
"addr" => addr,
"size" => size,
"offset" => offset,
"align" => align,
"reloff" => reloff,
"nreloc" => nreloc,
"flags" => flags,
"reserved1" => reserved1,
"reserved2" => reserved2,
}.merge super
end
end end
# Represents a section of a segment for 64-bit architectures. # Represents a section of a segment for 64-bit architectures.
@ -159,7 +178,7 @@ module MachO
attr_reader :reserved3 attr_reader :reserved3
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
FORMAT = "a16a16Q=2L=8".freeze FORMAT = "Z16Z16Q=2L=8".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
SIZEOF = 80 SIZEOF = 80
@ -171,6 +190,13 @@ module MachO
nreloc, flags, reserved1, reserved2) nreloc, flags, reserved1, reserved2)
@reserved3 = reserved3 @reserved3 = reserved3
end end
# @return [Hash] a hash representation of this {Section64}
def to_h
{
"reserved3" => reserved3,
}.merge super
end
end end
end end
end end

View File

@ -26,5 +26,15 @@ module MachO
new(*bin.unpack(format)) new(*bin.unpack(format))
end end
# @return [Hash] a hash representation of this {MachOStructure}.
def to_h
{
"structure" => {
"format" => self.class::FORMAT,
"bytesize" => self.class.bytesize,
},
}
end
end end
end end

View File

@ -22,6 +22,16 @@ module MachO
round(size, alignment) - size round(size, alignment) - size
end end
# Returns a string of null bytes of the requested (non-negative) size
# @param size [Integer] the size of the nullpad
# @return [String] the null string (or empty string, for `size = 0`)
# @raise [ArgumentError] if a non-positive nullpad is requested
def self.nullpad(size)
raise ArgumentError, "size < 0: #{size}" if size.negative?
"\x00" * size
end
# Converts an abstract (native-endian) String#unpack format to big or # Converts an abstract (native-endian) String#unpack format to big or
# little. # little.
# @param format [String] the format string being converted # @param format [String] the format string being converted
@ -46,11 +56,11 @@ module MachO
strings.each do |key, string| strings.each do |key, string|
offsets[key] = next_offset offsets[key] = next_offset
payload << string payload << string
payload << "\x00" payload << Utils.nullpad(1)
next_offset += string.bytesize + 1 next_offset += string.bytesize + 1
end end
payload << "\x00" * padding_for(fixed_offset + payload.bytesize, alignment) payload << Utils.nullpad(padding_for(fixed_offset + payload.bytesize, alignment))
[payload, offsets] [payload, offsets]
end end

View File

@ -19,5 +19,13 @@ module MachO
@endianness = endianness @endianness = endianness
@offset = offset @offset = offset
end end
# @return [Hash] a hash representation of this {MachOView}.
def to_h
{
"endianness" => endianness,
"offset" => offset,
}
end
end end
end end

View File

@ -26,10 +26,10 @@ GEM
execjs (2.7.0) execjs (2.7.0)
faraday (0.15.2) faraday (0.15.2)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
ffi (1.9.23) ffi (1.9.25)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
gemoji (3.0.0) gemoji (3.0.0)
github-pages (186) github-pages (187)
activesupport (= 4.2.10) activesupport (= 4.2.10)
github-pages-health-check (= 1.8.1) github-pages-health-check (= 1.8.1)
jekyll (= 3.7.3) jekyll (= 3.7.3)
@ -48,7 +48,7 @@ GEM
jekyll-relative-links (= 0.5.3) jekyll-relative-links (= 0.5.3)
jekyll-remote-theme (= 0.3.1) jekyll-remote-theme (= 0.3.1)
jekyll-sass-converter (= 1.5.2) jekyll-sass-converter (= 1.5.2)
jekyll-seo-tag (= 2.4.0) jekyll-seo-tag (= 2.5.0)
jekyll-sitemap (= 1.2.0) jekyll-sitemap (= 1.2.0)
jekyll-swiss (= 0.4.0) jekyll-swiss (= 0.4.0)
jekyll-theme-architect (= 0.1.1) jekyll-theme-architect (= 0.1.1)
@ -80,7 +80,7 @@ GEM
octokit (~> 4.0) octokit (~> 4.0)
public_suffix (~> 2.0) public_suffix (~> 2.0)
typhoeus (~> 1.3) typhoeus (~> 1.3)
html-pipeline (2.8.0) html-pipeline (2.8.3)
activesupport (>= 2) activesupport (>= 2)
nokogiri (>= 1.4) nokogiri (>= 1.4)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
@ -138,7 +138,7 @@ GEM
rubyzip (>= 1.2.1, < 3.0) rubyzip (>= 1.2.1, < 3.0)
jekyll-sass-converter (1.5.2) jekyll-sass-converter (1.5.2)
sass (~> 3.4) sass (~> 3.4)
jekyll-seo-tag (2.4.0) jekyll-seo-tag (2.5.0)
jekyll (~> 3.3) jekyll (~> 3.3)
jekyll-sitemap (1.2.0) jekyll-sitemap (1.2.0)
jekyll (~> 3.3) jekyll (~> 3.3)
@ -206,7 +206,7 @@ GEM
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
minitest (5.11.3) minitest (5.11.3)
multipart-post (2.0.0) multipart-post (2.0.0)
nokogiri (1.8.2) nokogiri (1.8.3)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
octokit (4.9.0) octokit (4.9.0)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
@ -237,7 +237,7 @@ GEM
ethon (>= 0.9.0) ethon (>= 0.9.0)
tzinfo (1.2.5) tzinfo (1.2.5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
unicode-display_width (1.3.3) unicode-display_width (1.4.0)
PLATFORMS PLATFORMS
ruby ruby