extract: rework to search through old/deleted formulae

- Removed use of FormulaVersions - it's generally too brittle for our uses. We
  now interface directly with the Git repo via utils/git and monkeypatch the
  formula loading process where needed.

- Really old formulae (that specify fields as instance vars instead of methods)
  still need more work to be supported; they don't work here quite yet.

- Properly handles deleted/removed formulae from Homebrew/homebrew-core.
  Additional work is still needed to search through Homebrew/legacy-homebrew if
  this functionality is desired.
This commit is contained in:
Caleb Xu 2018-08-02 00:21:09 -04:00
parent 8d8d235019
commit ff8b5f8c5a

View File

@ -12,10 +12,56 @@
#: recent version that can be found will be used. #: recent version that can be found will be used.
require "utils/git" require "utils/git"
require "formula_versions"
require "formulary" require "formulary"
require "tap" require "tap"
class BottleSpecification
def method_missing(m, *_args, &_block)
if [:sha1, :md5].include?(m)
opoo "Formula has unknown or deprecated stanza: #{m}" if ARGV.debug?
else
super
end
end
end
class Module
def method_missing(m, *_args, &_block)
if [:sha1, :md5].include?(m)
opoo "Formula has unknown or deprecated stanza: #{m}" if ARGV.debug?
else
super
end
end
end
class DependencyCollector
def parse_symbol_spec(spec, tags)
case spec
when :x11 then X11Requirement.new(spec.to_s, tags)
when :xcode then XcodeRequirement.new(tags)
when :linux then LinuxRequirement.new(tags)
when :macos then MacOSRequirement.new(tags)
when :arch then ArchRequirement.new(tags)
when :java then JavaRequirement.new(tags)
when :osxfuse then OsxfuseRequirement.new(tags)
when :tuntap then TuntapRequirement.new(tags)
when :ld64 then ld64_dep_if_needed(tags)
else
opoo "Unsupported special dependency #{spec.inspect}" if ARGV.debug?
end
end
module Compat
def parse_string_spec(spec, tags)
opoo "'depends_on ... => :run' is disabled. There is no replacement." if tags.include?(:run) && ARGV.debug?
super
end
end
prepend Compat
end
module Homebrew module Homebrew
module_function module_function
@ -35,56 +81,73 @@ module Homebrew
odie "The tap to which the formula is extracted must be specified!" if args.tap.nil? odie "The tap to which the formula is extracted must be specified!" if args.tap.nil?
formula = Formulary.factory(ARGV.named.first) begin
if args.version.nil? formula = Formulary.factory(ARGV.named.first)
version = formula.version name = formula.name
else repo = formula.path.parent.parent
version = args.version file = formula.path
rescue FormulaUnavailableError => e
opoo "'#{ARGV.named.first}' does not currently exist in the core tap" if ARGV.debug?
core = Tap.fetch("homebrew/core")
name = ARGV.named.first.downcase
repo = core.path
file = core.path.join("Formula", "#{name}.rb")
end end
destination_tap = Tap.fetch(args.tap) destination_tap = Tap.fetch(args.tap)
destination_tap.install unless destination_tap.installed? destination_tap.install unless destination_tap.installed?
odie "Cannot extract formula to homebrew/core!" if destination_tap.name == "homebrew/core" odie "Cannot extract formula to homebrew/core!" if destination_tap.name == "homebrew/core"
path = Pathname.new("#{destination_tap.path}/Formula/#{formula}@#{version}.rb") if args.version.nil?
rev = Git.last_revision_commit_of_file(repo, file)
version = formula_at_revision(repo, name, file, rev).version
odie "Could not find #{name}! The formula or version may not have existed." if rev.empty?
result = Git.last_revision_of_file(repo, file)
else
version = args.version
rev = "HEAD"
test_formula = nil
loop do
loop do
rev = Git.last_revision_commit_of_file(repo, file, before_commit: "#{rev}~1")
break if rev.empty?
break unless Git.last_revision_of_file(repo, file, before_commit: rev).empty?
ohai "Skipping revision #{rev} - file is empty at this revision" if ARGV.debug?
end
test_formula = formula_at_revision(repo, name, file, rev)
break if test_formula.nil? || test_formula.version == version
ohai "Trying #{test_formula.version} from revision #{rev} against desired #{version}" if ARGV.debug?
end
odie "Could not find #{name}! The formula or version may not have existed." if test_formula.nil?
result = Git.last_revision_of_file(repo, file, before_commit: rev)
end
# The class name has to be renamed to match the new filename, e.g. Foo version 1.2.3 becomes FooAT123 and resides in Foo@1.2.3.rb.
class_name = name.capitalize
versioned_name = Formulary.class_s("#{class_name}@#{version}")
result.gsub!("class #{class_name} < Formula", "class #{versioned_name} < Formula")
path = destination_tap.path.join("Formula", "#{name}@#{version}.rb")
if path.exist? if path.exist?
unless ARGV.force? unless ARGV.force?
odie <<~EOS odie <<~EOS
Destination formula already exists: #{path} Destination formula already exists: #{path}
To overwrite it and continue anyways, run: To overwrite it and continue anyways, run:
`brew extract #{formula} --version=#{version} --tap=#{destination_tap.name} --force` `brew extract #{name} --version=#{version} --tap=#{destination_tap.name} --force`
EOS EOS
end end
ohai "Overwriting existing formula at #{path}" if ARGV.debug? ohai "Overwriting existing formula at #{path}" if ARGV.debug?
path.delete path.delete
end end
ohai "Writing formula for #{name} from #{rev} to #{path}"
if args.version.nil?
rev = Git.last_revision_commit_of_file(formula.path.parent.parent, formula.path)
odie "Could not find #{formula} #{version}!" if rev.empty?
version_resolver = FormulaVersions.new(formula)
else
rev = "HEAD"
version_resolver = FormulaVersions.new(formula)
until version_resolver.formula_at_revision(rev) { |f| version_matches?(f, version, rev) || rev.empty? } do
rev = Git.last_revision_commit_of_file(formula.path.parent.parent, formula.path, before_commit: "#{rev}~1")
end
odie "Could not find #{formula} #{version}!" if rev.empty?
end
result = version_resolver.file_contents_at_revision(rev)
# The class name has to be renamed to match the new filename, e.g. Foo version 1.2.3 becomes FooAT123 and resides in Foo@1.2.3.rb.
name = formula.name.capitalize
versioned_name = Formulary.class_s("#{name}@#{version}")
result.gsub!("class #{name} < Formula", "class #{versioned_name} < Formula")
ohai "Writing formula for #{formula} from #{rev} to #{path}"
path.write result path.write result
end end
# @private # @private
def version_matches?(formula, version, rev) def formula_at_revision(repo, name, file, rev)
ohai "Trying #{formula.version} from revision #{rev} against desired #{version}" if ARGV.debug? return nil if rev.empty?
formula.version == version contents = Git.last_revision_of_file(repo, file, before_commit: rev)
Formulary.from_contents(name, file, contents)
end end
end end