diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index b2b3eb7c07..50439ccf9d 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -133,9 +133,14 @@ module Homebrew raise "No devel block is defined for #{f.full_name}" end - if f.installed? - msg = "#{f.full_name}-#{f.installed_version} already installed" - msg << ", it's just not linked" unless f.linked_keg.symlink? || f.keg_only? + current = f if f.installed? + current ||= f.old_installed_formulae.first + + if current + msg = "#{current.full_name}-#{current.installed_version} already installed" + unless current.linked_keg.symlink? || current.keg_only? + msg << ", it's just not linked" + end opoo msg elsif f.migration_needed? && !ARGV.force? # Check if the formula we try to install is the same as installed diff --git a/Library/Homebrew/cmd/outdated.rb b/Library/Homebrew/cmd/outdated.rb index 7afa41df4a..dfb64a282a 100644 --- a/Library/Homebrew/cmd/outdated.rb +++ b/Library/Homebrew/cmd/outdated.rb @@ -43,15 +43,28 @@ module Homebrew outdated_formulae.each do |f| if verbose - outdated_versions = f.outdated_versions(fetch_head: fetch_head) - current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s } + outdated_kegs = f.outdated_kegs(fetch_head: fetch_head) + + current_version = if f.alias_changed? + latest = f.latest_formula + "#{latest.name} (#{latest.pkg_version})" + elsif f.head? && outdated_kegs.any? { |k| k.version.to_s == f.pkg_version.to_s } + # There is a newer HEAD but the version number has not changed. "latest HEAD" else f.pkg_version.to_s end - puts "#{f.full_name} (#{outdated_versions.join(", ")}) < #{current_version}" + + outdated_versions = outdated_kegs. + group_by { |keg| Formulary.from_keg(keg) }. + sort_by { |formula, kegs| formula.full_name }. + map do |formula, kegs| + "#{formula.full_name} (#{kegs.map(&:version).join(", ")})" + end.join(", ") + + puts "#{outdated_versions} < #{current_version}" else - puts f.full_name + puts f.full_installed_specified_name end end end @@ -62,7 +75,7 @@ module Homebrew outdated_formulae = formulae.select { |f| f.outdated?(fetch_head: fetch_head) } outdated = outdated_formulae.each do |f| - outdated_versions = f.outdated_versions(fetch_head: fetch_head) + outdated_versions = f.outdated_kegs(fetch_head: fetch_head).map(&:version) current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s } "HEAD" else diff --git a/Library/Homebrew/cmd/upgrade.rb b/Library/Homebrew/cmd/upgrade.rb index 6968fbda88..c56a733843 100644 --- a/Library/Homebrew/cmd/upgrade.rb +++ b/Library/Homebrew/cmd/upgrade.rb @@ -37,10 +37,10 @@ module Homebrew (ARGV.resolved_formulae - outdated).each do |f| versions = f.installed_kegs.map(&:version) if versions.empty? - onoe "#{f.full_name} not installed" + onoe "#{f.full_specified_name} not installed" else version = versions.max - onoe "#{f.full_name} #{version} already installed" + onoe "#{f.full_specified_name} #{version} already installed" end end exit 1 if outdated.empty? @@ -51,19 +51,21 @@ module Homebrew outdated -= pinned end - if outdated.empty? + formulae_to_install = outdated.map(&:latest_formula) + + if formulae_to_install.empty? oh1 "No packages to upgrade" else - oh1 "Upgrading #{outdated.length} outdated package#{plural(outdated.length)}, with result:" - puts outdated.map { |f| "#{f.full_name} #{f.pkg_version}" } * ", " + oh1 "Upgrading #{formulae_to_install.length} outdated package#{plural(formulae_to_install.length)}, with result:" + puts formulae_to_install.map { |f| "#{f.full_specified_name} #{f.pkg_version}" } * ", " end unless upgrade_pinned? || pinned.empty? oh1 "Not upgrading #{pinned.length} pinned package#{plural(pinned.length)}:" - puts pinned.map { |f| "#{f.full_name} #{f.pkg_version}" } * ", " + puts pinned.map { |f| "#{f.full_specified_name} #{f.pkg_version}" } * ", " end - outdated.each do |f| + formulae_to_install.each do |f| upgrade_formula(f) next unless ARGV.include?("--cleanup") next unless f.installed? @@ -76,7 +78,11 @@ module Homebrew end def upgrade_formula(f) - outdated_keg = Keg.new(f.linked_keg.resolved_path) if f.linked_keg.directory? + formulae_maybe_with_kegs = [f] + f.old_installed_formulae + outdated_kegs = formulae_maybe_with_kegs. + map(&:linked_keg). + select(&:directory?). + map { |k| Keg.new(k.resolved_path) } fi = FormulaInstaller.new(f) fi.options = f.build.used_options @@ -87,12 +93,12 @@ module Homebrew fi.debug = ARGV.debug? fi.prelude - oh1 "Upgrading #{f.full_name}" + oh1 "Upgrading #{f.full_specified_name}" # first we unlink the currently active keg for this formula otherwise it is # possible for the existing build to interfere with the build we are about to # do! Seriously, it happens! - outdated_keg.unlink if outdated_keg + outdated_kegs.each(&:unlink) fi.install fi.finish @@ -117,7 +123,7 @@ module Homebrew ensure # restore previous installation state if build failed begin - outdated_keg.link if outdated_keg && !f.installed? + outdated_kegs.each(&:link) if !f.installed? rescue nil end diff --git a/Library/Homebrew/extend/ARGV.rb b/Library/Homebrew/extend/ARGV.rb index 63e46a1e24..d9f5998771 100644 --- a/Library/Homebrew/extend/ARGV.rb +++ b/Library/Homebrew/extend/ARGV.rb @@ -37,11 +37,23 @@ module HomebrewArgvExtension f.version.update_commit(k.version.version.commit) if k.version.head? end end - f else rack = Formulary.to_rack(name) - Formulary.from_rack(rack, spec(nil)) + alias_path = Formulary.factory(name).alias_path + f = Formulary.from_rack(rack, spec(nil), alias_path: alias_path) end + + # If this formula was installed with an alias that has since changed, + # then it was specified explicitly in ARGV. (Using the alias would + # instead have found the new formula.) + # + # Because of this, the user is referring to this specific formula, + # not any formula targetted by the same alias, so in this context + # the formula shouldn't be considered outdated if the alias used to + # install it has changed. + f.follow_installed_alias = false + + f end end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index f40f541104..e3a70fc659 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -60,16 +60,24 @@ class Formula # e.g. `this-formula` attr_reader :name - # The name specified when installing this {Formula}. - # Could be the name of the {Formula}, or an alias. - # e.g. `another-name-for-this-formula` + # The path to the alias that was used to identify this {Formula}. + # e.g. `/usr/local/Library/Taps/homebrew/homebrew-core/Aliases/another-name-for-this-formula` attr_reader :alias_path + # The name of the alias that was used to identify this {Formula}. + # e.g. `another-name-for-this-formula` + attr_reader :alias_name + # The fully-qualified name of this {Formula}. # For core formula it's the same as {#name}. # e.g. `homebrew/tap-name/this-formula` attr_reader :full_name + # The fully-qualified alias referring to this {Formula}. + # For core formula it's the same as {#alias_name}. + # e.g. `homebrew/tap-name/another-name-for-this-formula` + attr_reader :full_alias_name + # The full path to this {Formula}. # e.g. `/usr/local/Library/Taps/homebrew/homebrew-core/Formula/this-formula.rb` attr_reader :path @@ -149,25 +157,31 @@ class Formula # @return [BuildOptions] attr_accessor :build + # A {Boolean} indicating whether this formula should be considered outdated + # if the target of the alias it was installed with has since changed. + # Defaults to true. + # @return [Boolean] + attr_accessor :follow_installed_alias + alias follow_installed_alias? follow_installed_alias + # @private def initialize(name, path, spec, alias_path: nil) @name = name @path = path @alias_path = alias_path + @alias_name = File.basename(alias_path) if alias_path @revision = self.class.revision || 0 @version_scheme = self.class.version_scheme || 0 - if path == Formulary.core_path(name) - @tap = CoreTap.instance - @full_name = name + @tap = if path == Formulary.core_path(name) + CoreTap.instance elsif path.to_s =~ HOMEBREW_TAP_PATH_REGEX - @tap = Tap.fetch($1, $2) - @full_name = "#{@tap}/#{name}" - else - @tap = nil - @full_name = name + Tap.fetch($1, $2) end + @full_name = get_full_name(name) + @full_alias_name = get_full_name(@alias_name) + set_spec :stable set_spec :devel set_spec :head @@ -183,6 +197,7 @@ class Formula validate_attributes! @build = active_spec.build @pin = FormulaPin.new(self) + @follow_installed_alias = true end # @private @@ -197,6 +212,16 @@ class Formula private + # Allow full name logic to be re-used between names, aliases, + # and installed aliases. + def get_full_name(name) + if name.nil? || @tap.nil? || @tap.core_tap? + name + else + "#{@tap}/#{name}" + end + end + def set_spec(name) spec = self.class.send(name) if spec.url @@ -228,11 +253,47 @@ class Formula public - # The path that was specified to find/install this formula. + # The alias path that was used to install this formula, if present. + # Can differ from alias_path, which is the alias used to find the formula, + # and is specified to this instance. + def installed_alias_path + path = build.source["path"] if build.is_a?(Tab) + path if path =~ %r{#{HOMEBREW_TAP_DIR_REGEX}/Aliases} + end + + def installed_alias_name + File.basename(installed_alias_path) if installed_alias_path + end + + def full_installed_alias_name + get_full_name(installed_alias_name) + end + + # The path that was specified to find this formula. def specified_path alias_path || path end + # The name specified to find this formula. + def specified_name + alias_name || name + end + + # The name (including tap) specified to find this formula. + def full_specified_name + full_alias_name || full_name + end + + # The name specified to install this formula. + def installed_specified_name + installed_alias_name || name + end + + # The name (including tap) specified to install this formula. + def full_installed_specified_name + full_installed_alias_name || full_name + end + # Is the currently active {SoftwareSpec} a {#stable} build? # @private def stable? @@ -503,13 +564,13 @@ class Formula prefix.parent end - # All of current installed prefix directories. + # All currently installed prefix directories. # @private def installed_prefixes rack.directory? ? rack.subdirs : [] end - # All of current installed kegs. + # All currently installed kegs. # @private def installed_kegs installed_prefixes.map { |dir| Keg.new(dir) } @@ -1059,39 +1120,88 @@ class Formula end # @private - def outdated_versions(options = {}) - @outdated_versions ||= Hash.new do |cache, key| + def outdated_kegs(options = {}) + @outdated_kegs ||= Hash.new do |cache, key| raise Migrator::MigrationNeededError, self if migration_needed? - cache[key] = _outdated_versions(key) + cache[key] = _outdated_kegs(key) end - @outdated_versions[options] + @outdated_kegs[options] end - def _outdated_versions(options = {}) - all_versions = [] + def _outdated_kegs(options = {}) + all_kegs = [] installed_kegs.each do |keg| + all_kegs << keg version = keg.version - all_versions << version next if version.head? tab = Tab.for_keg(keg) next if version_scheme > tab.version_scheme next if version_scheme == tab.version_scheme && pkg_version > version - return [] + + # don't consider this keg current if there's a newer formula available + next if follow_installed_alias? && new_formula_available? + + return [] # this keg is the current version of the formula, so it's not outdated end + # Even if this formula hasn't been installed, there may be installations + # of other formulae which used to be targets of the alias currently + # targetting this formula. These should be counted as outdated versions. + all_kegs.concat old_installed_formulae.flat_map(&:installed_kegs) + head_version = latest_head_version if head_version && !head_version_outdated?(head_version, options) [] else - all_versions.sort + all_kegs.sort_by(&:version) end end + def new_formula_available? + installed_alias_target_changed? && !latest_formula.installed? + end + + def current_installed_alias_target + Formulary.factory(installed_alias_path) if installed_alias_path + end + + # Has the target of the alias used to install this formula changed? + # Returns false if the formula wasn't installed with an alias. + def installed_alias_target_changed? + target = current_installed_alias_target + target && target != self + end + + # Is this formula the target of an alias used to install an old formula? + def supersedes_an_installed_formula? + old_installed_formulae.any? + end + + # Has the alias used to install the formula changed, or are different + # formulae already installed with this alias? + def alias_changed? + installed_alias_target_changed? || supersedes_an_installed_formula? + end + + # If the alias has changed value, return the new formula. + # Otherwise, return self. + def latest_formula + installed_alias_target_changed? ? current_installed_alias_target : self + end + + def old_installed_formulae + # If this formula isn't the current target of the alias, + # it doesn't make sense to say that other formulae are older versions of it + # because we don't know which came first. + return [] if alias_path.nil? || installed_alias_target_changed? + self.class.installed_with_alias_path(alias_path) - [self] + end + # @private def outdated?(options = {}) - !outdated_versions(options).empty? + !outdated_kegs(options).empty? rescue Migrator::MigrationNeededError true end @@ -1246,6 +1356,11 @@ class Formula end.compact end + def self.installed_with_alias_path(alias_path) + return [] if alias_path.nil? + installed.select { |f| f.installed_alias_path == alias_path } + end + # an array of all alias files of core {Formula} # @private def self.core_alias_files diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 1cd9dce2ae..4c20c2225b 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -77,8 +77,11 @@ class Formulary end # Gets the formula instance. - def get_formula(spec) - klass.new(name, path, spec, alias_path: alias_path) + # + # `alias_path` can be overridden here in case an alias was used to refer to + # a formula that was loaded in another way. + def get_formula(spec, alias_path: nil) + klass.new(name, path, spec, alias_path: alias_path || self.alias_path) end def klass @@ -103,7 +106,7 @@ class Formulary super name, Formulary.path(full_name) end - def get_formula(spec) + def get_formula(spec, alias_path: nil) formula = super formula.local_bottle_path = @bottle_filename formula_version = formula.pkg_version @@ -120,7 +123,7 @@ class Formulary path = alias_path.resolved_path name = path.basename(".rb").to_s super name, path - @alias_path = alias_path + @alias_path = alias_path.to_s end end @@ -175,7 +178,7 @@ class Formulary super name, path end - def get_formula(spec) + def get_formula(spec, alias_path: nil) super rescue FormulaUnavailableError => e raise TapFormulaUnavailableError.new(tap, name), "", e.backtrace @@ -187,7 +190,7 @@ class Formulary super name, Formulary.core_path(name) end - def get_formula(_spec) + def get_formula(_spec, alias_path: nil) raise FormulaUnavailableError, name end end @@ -215,38 +218,42 @@ class Formulary # * a formula pathname # * a formula URL # * a local bottle reference - def self.factory(ref, spec = :stable) - loader_for(ref).get_formula(spec) + def self.factory(ref, spec = :stable, alias_path: nil) + loader_for(ref).get_formula(spec, alias_path: alias_path) end # Return a Formula instance for the given rack. # It will auto resolve formula's spec when requested spec is nil - def self.from_rack(rack, spec = nil) + # + # The :alias_path option will be used if the formula is found not to be + # installed, and discarded if it is installed because the alias_path used + # to install the formula will be set instead. + def self.from_rack(rack, spec = nil, alias_path: nil) kegs = rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : [] keg = kegs.detect(&:linked?) || kegs.detect(&:optlinked?) || kegs.max_by(&:version) if keg - from_keg(keg, spec) + from_keg(keg, spec, :alias_path => alias_path) else - factory(rack.basename.to_s, spec || :stable) + factory(rack.basename.to_s, spec || :stable, :alias_path => alias_path) end end # Return a Formula instance for the given keg. # It will auto resolve formula's spec when requested spec is nil - def self.from_keg(keg, spec = nil) + def self.from_keg(keg, spec = nil, alias_path: nil) tab = Tab.for_keg(keg) tap = tab.tap spec ||= tab.spec f = if tap.nil? - factory(keg.rack.basename.to_s, spec) + factory(keg.rack.basename.to_s, spec, :alias_path => alias_path) else begin - factory("#{tap}/#{keg.rack.basename}", spec) + factory("#{tap}/#{keg.rack.basename}", spec, :alias_path => alias_path) rescue FormulaUnavailableError # formula may be migrated to different tap. Try to search in core and all taps. - factory(keg.rack.basename.to_s, spec) + factory(keg.rack.basename.to_s, spec, :alias_path => alias_path) end end f.build = tab @@ -346,7 +353,7 @@ class Formulary end def self.core_path(name) - CoreTap.instance.formula_dir/"#{name.downcase}.rb" + CoreTap.instance.formula_dir/"#{name.to_s.downcase}.rb" end def self.tap_paths(name, taps = Dir["#{HOMEBREW_LIBRARY}/Taps/*/*/"]) diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb index 7c09e765f2..1da60c2143 100644 --- a/Library/Homebrew/test/test_formula.rb +++ b/Library/Homebrew/test/test_formula.rb @@ -8,15 +8,153 @@ class FormulaTests < Homebrew::TestCase name = "formula_name" path = Formulary.core_path(name) spec = :stable - alias_path = CoreTap.instance.alias_dir/"formula_alias" + + f = klass.new(name, path, spec) + assert_equal name, f.name + assert_equal name, f.specified_name + assert_equal name, f.full_name + assert_equal name, f.full_specified_name + assert_equal path, f.path + assert_nil f.alias_path + assert_nil f.alias_name + assert_nil f.full_alias_name + assert_raises(ArgumentError) { klass.new } + end + + def test_formula_instantiation_with_alias + klass = Class.new(Formula) { url "http://example.com/foo-1.0.tar.gz" } + name = "formula_name" + path = Formulary.core_path(name) + spec = :stable + alias_name = "baz@1" + alias_path = CoreTap.instance.alias_dir/alias_name f = klass.new(name, path, spec, alias_path: alias_path) assert_equal name, f.name + assert_equal name, f.full_name assert_equal path, f.path assert_equal alias_path, f.alias_path + assert_equal alias_name, f.alias_name + assert_equal alias_name, f.specified_name + assert_equal alias_name, f.full_alias_name + assert_equal alias_name, f.full_specified_name assert_raises(ArgumentError) { klass.new } end + def test_tap_formula_instantiation + tap = Tap.new("foo", "bar") + klass = Class.new(Formula) { url "baz-1.0" } + name = "baz" + full_name = "#{tap.user}/#{tap.repo}/#{name}" + path = tap.path/"Formula/#{name}.rb" + spec = :stable + + f = klass.new(name, path, spec) + assert_equal name, f.name + assert_equal name, f.specified_name + assert_equal full_name, f.full_name + assert_equal full_name, f.full_specified_name + assert_equal path, f.path + assert_nil f.alias_path + assert_nil f.alias_name + assert_nil f.full_alias_name + assert_raises(ArgumentError) { klass.new } + end + + def test_tap_formula_instantiation_with_alias + tap = Tap.new("foo", "bar") + klass = Class.new(Formula) { url "baz-1.0" } + name = "baz" + full_name = "#{tap.user}/#{tap.repo}/#{name}" + path = tap.path/"Formula/#{name}.rb" + spec = :stable + alias_name = "baz@1" + full_alias_name = "#{tap.user}/#{tap.repo}/#{alias_name}" + alias_path = CoreTap.instance.alias_dir/alias_name + + f = klass.new(name, path, spec, alias_path: alias_path) + assert_equal name, f.name + assert_equal full_name, f.full_name + assert_equal path, f.path + assert_equal alias_path, f.alias_path + assert_equal alias_name, f.alias_name + assert_equal alias_name, f.specified_name + assert_equal full_alias_name, f.full_alias_name + assert_equal full_alias_name, f.full_specified_name + assert_raises(ArgumentError) { klass.new } + end + + def test_follow_installed_alias + f = formula { url "foo-1.0" } + assert_predicate f, :follow_installed_alias? + + f.follow_installed_alias = true + assert_predicate f, :follow_installed_alias? + + f.follow_installed_alias = false + refute_predicate f, :follow_installed_alias? + end + + def test_installed_alias_with_core + f = formula { url "foo-1.0" } + + build_values_with_no_installed_alias = [ + nil, + BuildOptions.new({}, {}), + Tab.new(source: { "path" => f.path.to_s }), + ] + + build_values_with_no_installed_alias.each do |build| + f.build = build + assert_nil f.installed_alias_path + assert_nil f.installed_alias_name + assert_nil f.full_installed_alias_name + assert_equal f.name, f.installed_specified_name + assert_equal f.name, f.full_installed_specified_name + end + + alias_name = "bar" + alias_path = "#{CoreTap.instance.alias_dir}/#{alias_name}" + f.build = Tab.new(source: { "path" => alias_path }) + assert_equal alias_path, f.installed_alias_path + assert_equal alias_name, f.installed_alias_name + assert_equal alias_name, f.full_installed_alias_name + assert_equal alias_name, f.installed_specified_name + assert_equal alias_name, f.full_installed_specified_name + end + + def test_installed_alias_with_tap + tap = Tap.new("user", "repo") + name = "foo" + path = "#{tap.path}/Formula/#{name}.rb" + f = formula(name, path) { url "foo-1.0" } + + build_values_with_no_installed_alias = [ + nil, + BuildOptions.new({}, {}), + Tab.new(source: { "path" => f.path }), + ] + + build_values_with_no_installed_alias.each do |build| + f.build = build + assert_nil f.installed_alias_path + assert_nil f.installed_alias_name + assert_nil f.full_installed_alias_name + assert_equal f.name, f.installed_specified_name + assert_equal f.full_name, f.full_installed_specified_name + end + + alias_name = "bar" + full_alias_name = "#{tap.user}/#{tap.repo}/#{alias_name}" + alias_path = "#{tap.alias_dir}/#{alias_name}" + f.build = Tab.new(source: { "path" => alias_path }) + assert_equal alias_path, f.installed_alias_path + assert_equal alias_name, f.installed_alias_name + assert_equal full_alias_name, f.full_installed_alias_name + assert_equal alias_name, f.installed_specified_name + assert_equal full_alias_name, f.full_installed_specified_name + end + def test_prefix f = Testball.new assert_equal HOMEBREW_CELLAR/f.name/"0.1", f.prefix @@ -248,6 +386,62 @@ class FormulaTests < Homebrew::TestCase assert_nil Testball.new <=> Object.new end + def test_alias_paths_with_build_options + alias_path = CoreTap.instance.alias_dir/"another_name" + f = formula(:alias_path => alias_path) { url "foo-1.0" } + f.build = BuildOptions.new({}, {}) + assert_equal alias_path, f.alias_path + assert_nil f.installed_alias_path + end + + def test_alias_paths_with_tab_with_non_alias_source_path + alias_path = CoreTap.instance.alias_dir/"another_name" + source_path = CoreTap.instance.formula_dir/"another_other_name" + f = formula(:alias_path => alias_path) { url "foo-1.0" } + f.build = Tab.new(:source => { "path" => source_path.to_s }) + assert_equal alias_path, f.alias_path + assert_nil f.installed_alias_path + end + + def test_alias_paths_with_tab_with_alias_source_path + alias_path = CoreTap.instance.alias_dir/"another_name" + source_path = CoreTap.instance.alias_dir/"another_other_name" + f = formula(:alias_path => alias_path) { url "foo-1.0" } + f.build = Tab.new(:source => { "path" => source_path.to_s }) + assert_equal alias_path, f.alias_path + assert_equal source_path.to_s, f.installed_alias_path + end + + def test_installed_with_alias_path_with_nil + assert_predicate Formula.installed_with_alias_path(nil), :empty? + end + + def test_installed_with_alias_path_with_a_path + alias_path = "#{CoreTap.instance.alias_dir}/alias" + different_alias_path = "#{CoreTap.instance.alias_dir}/another_alias" + + formula_with_alias = formula("foo") { url "foo-1.0" } + formula_with_alias.build = Tab.empty + formula_with_alias.build.source["path"] = alias_path + + formula_without_alias = formula("bar") { url "bar-1.0" } + formula_without_alias.build = Tab.empty + formula_without_alias.build.source["path"] = formula_without_alias.path.to_s + + formula_with_different_alias = formula("baz") { url "baz-1.0" } + formula_with_different_alias.build = Tab.empty + formula_with_different_alias.build.source["path"] = different_alias_path + + formulae = [ + formula_with_alias, + formula_without_alias, + formula_with_different_alias, + ] + + Formula.stubs(:installed).returns(formulae) + assert_equal [formula_with_alias], Formula.installed_with_alias_path(alias_path) + end + def test_formula_spec_integration f = formula do homepage "http://example.com" @@ -581,29 +775,113 @@ class FormulaTests < Homebrew::TestCase end end +class AliasChangeTests < Homebrew::TestCase + attr_reader :f, :new_formula, :tab, :alias_path + + def make_formula(version) + f = formula(alias_path: alias_path) { url "foo-#{version}" } + f.build = tab + f + end + + def setup + alias_name = "bar" + @alias_path = "#{CoreTap.instance.alias_dir}/#{alias_name}" + + @tab = Tab.empty + + @f = make_formula("1.0") + @new_formula = make_formula("1.1") + + Formula.stubs(:installed).returns([f]) + end + + def test_alias_changes_when_not_installed_with_alias + tab.source["path"] = Formulary.core_path(f.name).to_s + + assert_nil f.current_installed_alias_target + assert_equal f, f.latest_formula + refute_predicate f, :installed_alias_target_changed? + refute_predicate f, :supersedes_an_installed_formula? + refute_predicate f, :alias_changed? + assert_predicate f.old_installed_formulae, :empty? + end + + def test_alias_changes_when_not_changed + tab.source["path"] = alias_path + stub_formula_loader(f, alias_path) + + assert_equal f, f.current_installed_alias_target + assert_equal f, f.latest_formula + refute_predicate f, :installed_alias_target_changed? + refute_predicate f, :supersedes_an_installed_formula? + refute_predicate f, :alias_changed? + assert_predicate f.old_installed_formulae, :empty? + end + + def test_alias_changes_when_new_alias_target + tab.source["path"] = alias_path + stub_formula_loader(new_formula, alias_path) + + assert_equal new_formula, f.current_installed_alias_target + assert_equal new_formula, f.latest_formula + assert_predicate f, :installed_alias_target_changed? + refute_predicate f, :supersedes_an_installed_formula? + assert_predicate f, :alias_changed? + assert_predicate f.old_installed_formulae, :empty? + end + + def test_alias_changes_when_old_formulae_installed + tab.source["path"] = alias_path + stub_formula_loader(new_formula, alias_path) + + assert_equal new_formula, new_formula.current_installed_alias_target + assert_equal new_formula, new_formula.latest_formula + refute_predicate new_formula, :installed_alias_target_changed? + assert_predicate new_formula, :supersedes_an_installed_formula? + assert_predicate new_formula, :alias_changed? + assert_equal [f], new_formula.old_installed_formulae + end +end + class OutdatedVersionsTests < Homebrew::TestCase - attr_reader :outdated_prefix, :same_prefix, :greater_prefix, :head_prefix - attr_reader :f + attr_reader :outdated_prefix, + :same_prefix, + :greater_prefix, + :head_prefix, + :old_alias_target_prefix + attr_reader :f, :old_formula, :new_formula def setup @f = formula do url "foo" version "1.20" end + + @old_formula = formula("foo@1") { url "foo-1.0" } + @new_formula = formula("foo@2") { url "foo-2.0" } + @outdated_prefix = HOMEBREW_CELLAR/"#{f.name}/1.11" @same_prefix = HOMEBREW_CELLAR/"#{f.name}/1.20" @greater_prefix = HOMEBREW_CELLAR/"#{f.name}/1.21" @head_prefix = HOMEBREW_CELLAR/"#{f.name}/HEAD" + @old_alias_target_prefix = HOMEBREW_CELLAR/"#{old_formula.name}/1.0" end def teardown - @f.rack.rmtree if @f.rack.exist? + formulae = [@f, @old_formula, @new_formula] + formulae.map(&:rack).select(&:exist?).each(&:rmtree) + end + + def alias_path + "#{@f.tap.alias_dir}/bar" end def setup_tab_for_prefix(prefix, options = {}) prefix.mkpath tab = Tab.empty tab.tabfile = prefix.join("INSTALL_RECEIPT.json") + tab.source["path"] = options[:path].to_s if options[:path] tab.source["tap"] = options[:tap] if options[:tap] tab.source["versions"] = options[:versions] if options[:versions] tab.source_modified_time = options[:source_modified_time].to_i @@ -611,96 +889,148 @@ class OutdatedVersionsTests < Homebrew::TestCase tab end - def reset_outdated_versions - f.instance_variable_set(:@outdated_versions, nil) + def reset_outdated_kegs + f.instance_variable_set(:@outdated_kegs, nil) end def test_greater_different_tap_installed setup_tab_for_prefix(greater_prefix, tap: "user/repo") - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? end def test_greater_same_tap_installed f.instance_variable_set(:@tap, CoreTap.instance) setup_tab_for_prefix(greater_prefix, tap: "homebrew/core") - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? end def test_outdated_different_tap_installed setup_tab_for_prefix(outdated_prefix, tap: "user/repo") - refute_predicate f.outdated_versions, :empty? + refute_predicate f.outdated_kegs, :empty? end def test_outdated_same_tap_installed f.instance_variable_set(:@tap, CoreTap.instance) setup_tab_for_prefix(outdated_prefix, tap: "homebrew/core") - refute_predicate f.outdated_versions, :empty? + refute_predicate f.outdated_kegs, :empty? end - def test_same_head_installed + def test_outdated_follow_alias_and_alias_unchanged + f.follow_installed_alias = true + f.build = setup_tab_for_prefix(same_prefix, path: alias_path) + stub_formula_loader(f, alias_path) + assert_predicate f.outdated_kegs, :empty? + end + + def test_outdated_follow_alias_and_alias_changed_and_new_target_not_installed + f.follow_installed_alias = true + f.build = setup_tab_for_prefix(same_prefix, path: alias_path) + stub_formula_loader(new_formula, alias_path) + refute_predicate f.outdated_kegs, :empty? + end + + def test_outdated_follow_alias_and_alias_changed_and_new_target_installed + f.follow_installed_alias = true + f.build = setup_tab_for_prefix(same_prefix, path: alias_path) + stub_formula_loader(new_formula, alias_path) + setup_tab_for_prefix(new_formula.prefix) # install new_formula + assert_predicate f.outdated_kegs, :empty? + end + + def test_outdated_no_follow_alias_and_alias_unchanged + f.follow_installed_alias = false + f.build = setup_tab_for_prefix(same_prefix, path: alias_path) + stub_formula_loader(f, alias_path) + assert_predicate f.outdated_kegs, :empty? + end + + def test_outdated_no_follow_alias_and_alias_changed + f.follow_installed_alias = false + f.build = setup_tab_for_prefix(same_prefix, path: alias_path) + stub_formula_loader(formula("foo@2") { url "foo-2.0" }, alias_path) + assert_predicate f.outdated_kegs, :empty? + end + + def test_outdated_old_alias_targets_installed + @f = formula(alias_path: alias_path) { url "foo-1.0" } + tab = setup_tab_for_prefix(old_alias_target_prefix, path: alias_path) + old_formula.build = tab + Formula.stubs(:installed).returns([old_formula]) + refute_predicate f.outdated_kegs, :empty? + end + + def test_outdated_old_alias_targets_not_installed + @f = formula(alias_path: alias_path) { url "foo-1.0" } + tab = setup_tab_for_prefix(old_alias_target_prefix, path: old_formula.path) + old_formula.build = tab + Formula.stubs(:installed).returns([old_formula]) + assert_predicate f.outdated_kegs, :empty? + end + + def test_outdated_same_head_installed f.instance_variable_set(:@tap, CoreTap.instance) setup_tab_for_prefix(head_prefix, tap: "homebrew/core") - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? end - def test_different_head_installed + def test_outdated_different_head_installed f.instance_variable_set(:@tap, CoreTap.instance) setup_tab_for_prefix(head_prefix, tap: "user/repo") - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? end - def test_mixed_taps_greater_version_installed + def test_outdated_mixed_taps_greater_version_installed f.instance_variable_set(:@tap, CoreTap.instance) setup_tab_for_prefix(outdated_prefix, tap: "homebrew/core") setup_tab_for_prefix(greater_prefix, tap: "user/repo") - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? setup_tab_for_prefix(greater_prefix, tap: "homebrew/core") - reset_outdated_versions + reset_outdated_kegs - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? end - def test_mixed_taps_outdated_version_installed + def test_outdated_mixed_taps_outdated_version_installed f.instance_variable_set(:@tap, CoreTap.instance) extra_outdated_prefix = HOMEBREW_CELLAR/"#{f.name}/1.0" setup_tab_for_prefix(outdated_prefix) setup_tab_for_prefix(extra_outdated_prefix, tap: "homebrew/core") - reset_outdated_versions + reset_outdated_kegs - refute_predicate f.outdated_versions, :empty? + refute_predicate f.outdated_kegs, :empty? setup_tab_for_prefix(outdated_prefix, tap: "user/repo") - reset_outdated_versions + reset_outdated_kegs - refute_predicate f.outdated_versions, :empty? + refute_predicate f.outdated_kegs, :empty? end - def test_same_version_tap_installed + def test_outdated_same_version_tap_installed f.instance_variable_set(:@tap, CoreTap.instance) setup_tab_for_prefix(same_prefix, tap: "homebrew/core") - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? setup_tab_for_prefix(same_prefix, tap: "user/repo") - reset_outdated_versions + reset_outdated_kegs - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? end def test_outdated_installed_head_less_than_stable tab = setup_tab_for_prefix(head_prefix, versions: { "stable" => "1.0" }) - refute_predicate f.outdated_versions, :empty? + refute_predicate f.outdated_kegs, :empty? # Tab.for_keg(head_prefix) will be fetched from CACHE but we write it anyway tab.source["versions"] = { "stable" => f.version.to_s } tab.write - reset_outdated_versions + reset_outdated_kegs - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? end def test_outdated_fetch_head @@ -738,20 +1068,20 @@ class OutdatedVersionsTests < Homebrew::TestCase end end - refute_predicate f.outdated_versions(fetch_head: true), :empty? + refute_predicate f.outdated_kegs(fetch_head: true), :empty? tab_a.source["versions"] = { "stable" => f.version.to_s } tab_a.write - reset_outdated_versions - refute_predicate f.outdated_versions(fetch_head: true), :empty? + reset_outdated_kegs + refute_predicate f.outdated_kegs(fetch_head: true), :empty? head_prefix_a.rmtree - reset_outdated_versions - refute_predicate f.outdated_versions(fetch_head: true), :empty? + reset_outdated_kegs + refute_predicate f.outdated_kegs(fetch_head: true), :empty? setup_tab_for_prefix(head_prefix_c, source_modified_time: 1) - reset_outdated_versions - assert_predicate f.outdated_versions(fetch_head: true), :empty? + reset_outdated_kegs + assert_predicate f.outdated_kegs(fetch_head: true), :empty? ensure ENV.replace(initial_env) testball_repo.rmtree if testball_repo.exist? @@ -762,7 +1092,7 @@ class OutdatedVersionsTests < Homebrew::TestCase FileUtils.rm_rf HOMEBREW_CELLAR/"testball" end - def test_outdated_versions_version_scheme_changed + def test_outdated_kegs_version_scheme_changed @f = formula("testball") do url "foo" version "20141010" @@ -772,12 +1102,12 @@ class OutdatedVersionsTests < Homebrew::TestCase prefix = HOMEBREW_CELLAR.join("testball/0.1") setup_tab_for_prefix(prefix, versions: { "stable" => "0.1" }) - refute_predicate f.outdated_versions, :empty? + refute_predicate f.outdated_kegs, :empty? ensure prefix.rmtree end - def test_outdated_versions_mixed_version_schemes + def test_outdated_kegs_mixed_version_schemes @f = formula("testball") do url "foo" version "20141010" @@ -790,23 +1120,23 @@ class OutdatedVersionsTests < Homebrew::TestCase prefix_b = HOMEBREW_CELLAR.join("testball/2.14") setup_tab_for_prefix(prefix_b, versions: { "stable" => "2.14", "version_scheme" => 2 }) - refute_predicate f.outdated_versions, :empty? - reset_outdated_versions + refute_predicate f.outdated_kegs, :empty? + reset_outdated_kegs prefix_c = HOMEBREW_CELLAR.join("testball/20141009") setup_tab_for_prefix(prefix_c, versions: { "stable" => "20141009", "version_scheme" => 3 }) - refute_predicate f.outdated_versions, :empty? - reset_outdated_versions + refute_predicate f.outdated_kegs, :empty? + reset_outdated_kegs prefix_d = HOMEBREW_CELLAR.join("testball/20141011") setup_tab_for_prefix(prefix_d, versions: { "stable" => "20141009", "version_scheme" => 3 }) - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? ensure f.rack.rmtree end - def test_outdated_versions_head_with_version_scheme + def test_outdated_kegs_head_with_version_scheme @f = formula("testball") do url "foo" version "1.0" @@ -816,13 +1146,13 @@ class OutdatedVersionsTests < Homebrew::TestCase head_prefix = HOMEBREW_CELLAR.join("testball/HEAD") setup_tab_for_prefix(head_prefix, versions: { "stable" => "1.0", "version_scheme" => 1 }) - refute_predicate f.outdated_versions, :empty? + refute_predicate f.outdated_kegs, :empty? - reset_outdated_versions + reset_outdated_kegs head_prefix.rmtree setup_tab_for_prefix(head_prefix, versions: { "stable" => "1.0", "version_scheme" => 2 }) - assert_predicate f.outdated_versions, :empty? + assert_predicate f.outdated_kegs, :empty? ensure head_prefix.rmtree end diff --git a/Library/Homebrew/test/test_formulary.rb b/Library/Homebrew/test/test_formulary.rb index 8b3417dad9..c545ff84da 100644 --- a/Library/Homebrew/test/test_formulary.rb +++ b/Library/Homebrew/test/test_formulary.rb @@ -89,7 +89,7 @@ class FormularyFactoryTest < Homebrew::TestCase FileUtils.ln_s @path, alias_path result = Formulary.factory("foo") assert_kind_of Formula, result - assert_equal alias_path, result.alias_path + assert_equal alias_path.to_s, result.alias_path ensure alias_dir.rmtree end diff --git a/Library/Homebrew/test/testing_env.rb b/Library/Homebrew/test/testing_env.rb index cbbc5eff12..aa89ba2bd4 100644 --- a/Library/Homebrew/test/testing_env.rb +++ b/Library/Homebrew/test/testing_env.rb @@ -121,5 +121,13 @@ module Homebrew ENV.replace old end end + + # Use a stubbed {Formulary::FormulaLoader} to make a given formula be found + # when loading from {Formulary} with `ref`. + def stub_formula_loader(formula, ref = formula.name) + loader = mock + loader.stubs(:get_formula).returns(formula) + Formulary.stubs(:loader_for).with(ref).returns(loader) + end end end