diff --git a/Library/Homebrew/build.rb b/Library/Homebrew/build.rb index 1ba4a72793..7d53074cde 100644 --- a/Library/Homebrew/build.rb +++ b/Library/Homebrew/build.rb @@ -57,7 +57,7 @@ class Build build = effective_build_options_for(dependent) if dep.prune_from_option?(build) || dep.prune_if_build_and_not_dependent?(dependent, formula) || - (dep.test? && !dep.build?) + (dep.test? && !dep.build?) || dep.implicit? Dependency.prune elsif dep.build? Dependency.keep_but_prune_recursive_deps diff --git a/Library/Homebrew/cmd/deps.rb b/Library/Homebrew/cmd/deps.rb index 09736b135d..ab840001b5 100644 --- a/Library/Homebrew/cmd/deps.rb +++ b/Library/Homebrew/cmd/deps.rb @@ -191,6 +191,7 @@ module Homebrew str = "#{str} [test]" if dep.test? str = "#{str} [optional]" if dep.optional? str = "#{str} [recommended]" if dep.recommended? + str = "#{str} [implicit]" if dep.implicit? end str diff --git a/Library/Homebrew/dependable.rb b/Library/Homebrew/dependable.rb index bbea4446db..516cfa2e32 100644 --- a/Library/Homebrew/dependable.rb +++ b/Library/Homebrew/dependable.rb @@ -9,7 +9,7 @@ require "options" module Dependable # `:run` and `:linked` are no longer used but keep them here to avoid their # misuse in future. - RESERVED_TAGS = [:build, :optional, :recommended, :run, :test, :linked].freeze + RESERVED_TAGS = [:build, :optional, :recommended, :run, :test, :linked, :implicit].freeze attr_reader :tags @@ -29,6 +29,10 @@ module Dependable tags.include? :test end + def implicit? + tags.include? :implicit + end + def required? !build? && !test? && !optional? && !recommended? end diff --git a/Library/Homebrew/dependencies.rb b/Library/Homebrew/dependencies.rb index 018c9f438e..625a874210 100644 --- a/Library/Homebrew/dependencies.rb +++ b/Library/Homebrew/dependencies.rb @@ -33,6 +33,10 @@ class Dependencies < SimpleDelegator build + required + recommended end + def dup_without_system_deps + self.class.new(*__getobj__.reject { |dep| dep.uses_from_macos? && dep.use_macos_install? }) + end + sig { returns(String) } def inspect "#<#{self.class.name}: #{__getobj__}>" diff --git a/Library/Homebrew/dependencies_helpers.rb b/Library/Homebrew/dependencies_helpers.rb index 8006882b11..dd2a3acd2e 100644 --- a/Library/Homebrew/dependencies_helpers.rb +++ b/Library/Homebrew/dependencies_helpers.rb @@ -56,11 +56,7 @@ module DependenciesHelpers # If a tap isn't installed, we can't find the dependencies of one of # its formulae, and an exception will be thrown if we try. - if klass == Dependency && - dep.is_a?(TapDependency) && - !dep.tap.installed? - Dependency.keep_but_prune_recursive_deps - end + Dependency.keep_but_prune_recursive_deps if klass == Dependency && dep.tap && !dep.tap.installed? end end diff --git a/Library/Homebrew/dependency.rb b/Library/Homebrew/dependency.rb index 14b4b9d48b..d85303b492 100644 --- a/Library/Homebrew/dependency.rb +++ b/Library/Homebrew/dependency.rb @@ -11,18 +11,20 @@ class Dependency include Dependable extend Cachable - attr_reader :name, :env_proc, :option_names + attr_reader :name, :env_proc, :option_names, :tap DEFAULT_ENV_PROC = proc {}.freeze private_constant :DEFAULT_ENV_PROC - def initialize(name, tags = [], env_proc = DEFAULT_ENV_PROC, option_names = [name]) + def initialize(name, tags = [], env_proc = DEFAULT_ENV_PROC, option_names = [name&.split("/")&.last]) raise ArgumentError, "Dependency must have a name!" unless name @name = name @tags = tags @env_proc = env_proc @option_names = option_names + + @tap = Tap.fetch(Regexp.last_match(1), Regexp.last_match(2)) if name =~ HOMEBREW_TAP_FORMULA_REGEX end def to_s @@ -46,6 +48,8 @@ class Dependency def installed? to_formula.latest_version_installed? + rescue FormulaUnavailableError + false end def satisfied?(inherited_options = []) @@ -65,6 +69,11 @@ class Dependency env_proc&.call end + sig { overridable.returns(T::Boolean) } + def uses_from_macos? + false + end + sig { returns(String) } def inspect "#<#{self.class.name}: #{name.inspect} #{tags.inspect}>" @@ -169,7 +178,14 @@ class Dependency dep = deps.first tags = merge_tags(deps) option_names = deps.flat_map(&:option_names).uniq - dep.class.new(name, tags, dep.env_proc, option_names) + kwargs = {} + kwargs[:bounds] = dep.bounds if dep.uses_from_macos? + # TODO: simpify to just **kwargs when we require Ruby >= 2.7 + if kwargs.empty? + dep.class.new(name, tags, dep.env_proc, option_names) + else + dep.class.new(name, tags, dep.env_proc, option_names, **kwargs) + end end end @@ -197,26 +213,52 @@ class Dependency end def merge_temporality(deps) - # Means both build and runtime dependency. - return [] unless deps.all?(&:build?) - - [:build] + new_tags = [] + new_tags << :build if deps.all?(&:build?) + new_tags << :implicit if deps.all?(&:implicit?) + new_tags end end end -# A dependency on another Homebrew formula in a specific tap. -class TapDependency < Dependency - attr_reader :tap +# A dependency that marked as "installed" on macOS +class UsesFromMacOSDependency < Dependency + attr_reader :bounds - def initialize(name, tags = [], env_proc = DEFAULT_ENV_PROC, option_names = [name.split("/").last]) - @tap = Tap.fetch(name.rpartition("/").first) + def initialize(name, tags = [], env_proc = DEFAULT_ENV_PROC, option_names = [name], bounds:) super(name, tags, env_proc, option_names) + + @bounds = bounds end def installed? - super - rescue FormulaUnavailableError + use_macos_install? || super + end + + sig { returns(T::Boolean) } + def use_macos_install? + # Check whether macOS is new enough for dependency to not be required. + if Homebrew::SimulateSystem.simulating_or_running_on_macos? + # Assume the oldest macOS version when simulating a generic macOS version + return true if Homebrew::SimulateSystem.current_os == :macos && !bounds.key?(:since) + + if Homebrew::SimulateSystem.current_os != :macos + current_os = MacOSVersion.from_symbol(Homebrew::SimulateSystem.current_os) + since_os = MacOSVersion.from_symbol(bounds[:since]) if bounds.key?(:since) + return true if current_os >= since_os + end + end + false end + + sig { override.returns(T::Boolean) } + def uses_from_macos? + true + end + + sig { override.params(formula: Formula).returns(T.self_type) } + def dup_with_formula_name(formula) + self.class.new(formula.full_name.to_s, tags, env_proc, option_names, bounds: bounds) + end end diff --git a/Library/Homebrew/dependency_collector.rb b/Library/Homebrew/dependency_collector.rb index b05addd086..6a1021ef57 100644 --- a/Library/Homebrew/dependency_collector.rb +++ b/Library/Homebrew/dependency_collector.rb @@ -84,37 +84,37 @@ class DependencyCollector def git_dep_if_needed(tags) return if Utils::Git.available? - Dependency.new("git", tags) + Dependency.new("git", [*tags, :implicit]) end def curl_dep_if_needed(tags) - Dependency.new("curl", tags) + Dependency.new("curl", [*tags, :implicit]) end def subversion_dep_if_needed(tags) return if Utils::Svn.available? - Dependency.new("subversion", tags) + Dependency.new("subversion", [*tags, :implicit]) end def cvs_dep_if_needed(tags) - Dependency.new("cvs", tags) unless which("cvs") + Dependency.new("cvs", [*tags, :implicit]) unless which("cvs") end def xz_dep_if_needed(tags) - Dependency.new("xz", tags) unless which("xz") + Dependency.new("xz", [*tags, :implicit]) unless which("xz") end def zstd_dep_if_needed(tags) - Dependency.new("zstd", tags) unless which("zstd") + Dependency.new("zstd", [*tags, :implicit]) unless which("zstd") end def unzip_dep_if_needed(tags) - Dependency.new("unzip", tags) unless which("unzip") + Dependency.new("unzip", [*tags, :implicit]) unless which("unzip") end def bzip2_dep_if_needed(tags) - Dependency.new("bzip2", tags) unless which("bzip2") + Dependency.new("bzip2", [*tags, :implicit]) unless which("bzip2") end def self.tar_needs_xz_dependency? @@ -127,6 +127,8 @@ class DependencyCollector def init_global_dep_tree_if_needed!; end def parse_spec(spec, tags) + raise ArgumentError, "Implicit dependencies cannot be manually specified" if tags.include?(:implicit) + case spec when String parse_string_spec(spec, tags) @@ -144,11 +146,7 @@ class DependencyCollector end def parse_string_spec(spec, tags) - if spec.match?(HOMEBREW_TAP_FORMULA_REGEX) - TapDependency.new(spec, tags) - else - Dependency.new(spec, tags) - end + Dependency.new(spec, tags) end def parse_symbol_spec(spec, tags) @@ -188,11 +186,11 @@ class DependencyCollector elsif strategy <= SubversionDownloadStrategy subversion_dep_if_needed(tags) elsif strategy <= MercurialDownloadStrategy - Dependency.new("mercurial", tags) + Dependency.new("mercurial", [*tags, :implicit]) elsif strategy <= FossilDownloadStrategy - Dependency.new("fossil", tags) + Dependency.new("fossil", [*tags, :implicit]) elsif strategy <= BazaarDownloadStrategy - Dependency.new("breezy", tags) + Dependency.new("breezy", [*tags, :implicit]) elsif strategy <= CVSDownloadStrategy cvs_dep_if_needed(tags) elsif strategy < AbstractDownloadStrategy @@ -208,10 +206,10 @@ class DependencyCollector when ".zst" then zstd_dep_if_needed(tags) when ".zip" then unzip_dep_if_needed(tags) when ".bz2" then bzip2_dep_if_needed(tags) - when ".lha", ".lzh" then Dependency.new("lha", tags) - when ".lz" then Dependency.new("lzip", tags) - when ".rar" then Dependency.new("libarchive", tags) - when ".7z" then Dependency.new("p7zip", tags) + when ".lha", ".lzh" then Dependency.new("lha", [*tags, :implicit]) + when ".lz" then Dependency.new("lzip", [*tags, :implicit]) + when ".rar" then Dependency.new("libarchive", [*tags, :implicit]) + when ".7z" then Dependency.new("p7zip", [*tags, :implicit]) end end end diff --git a/Library/Homebrew/extend/os/linux/dependency_collector.rb b/Library/Homebrew/extend/os/linux/dependency_collector.rb index e049afcaf6..2e0952b4bc 100644 --- a/Library/Homebrew/extend/os/linux/dependency_collector.rb +++ b/Library/Homebrew/extend/os/linux/dependency_collector.rb @@ -17,7 +17,7 @@ class DependencyCollector return if global_dep_tree[GCC]&.intersect?(related_formula_names) return unless formula_for(GCC) - Dependency.new(GCC) + Dependency.new(GCC, [:implicit]) end sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) } @@ -28,7 +28,7 @@ class DependencyCollector return if global_dep_tree[GLIBC]&.intersect?(related_formula_names) return unless formula_for(GLIBC) - Dependency.new(GLIBC) + Dependency.new(GLIBC, [:implicit]) end private diff --git a/Library/Homebrew/extend/os/mac/dependency_collector.rb b/Library/Homebrew/extend/os/mac/dependency_collector.rb index 9387bbe50f..6f97069e70 100644 --- a/Library/Homebrew/extend/os/mac/dependency_collector.rb +++ b/Library/Homebrew/extend/os/mac/dependency_collector.rb @@ -8,11 +8,11 @@ class DependencyCollector def git_dep_if_needed(tags); end def subversion_dep_if_needed(tags) - Dependency.new("subversion", tags) + Dependency.new("subversion", [*tags, :implicit]) end def cvs_dep_if_needed(tags) - Dependency.new("cvs", tags) + Dependency.new("cvs", [*tags, :implicit]) end def xz_dep_if_needed(tags); end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index f6f2cf5522..47b1bf189b 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -210,8 +210,9 @@ class Formula @full_name = full_name_with_optional_tap(name) @full_alias_name = full_name_with_optional_tap(@alias_name) - spec_eval :stable - spec_eval :head + self.class.spec_syms.each do |sym| + spec_eval sym + end @active_spec = determine_active_spec(spec) @active_spec_sym = if head? @@ -533,6 +534,9 @@ class Formula # The {Dependency}s for the currently active {SoftwareSpec}. delegate deps: :active_spec + # The declared {Dependency}s for the currently active {SoftwareSpec} (i.e. including those provided by macOS) + delegate declared_deps: :active_spec + # Dependencies provided by macOS for the currently active {SoftwareSpec}. delegate uses_from_macos_elements: :active_spec @@ -2111,9 +2115,31 @@ class Formula Checksum.new(Digest::SHA256.file(path).hexdigest) if path.exist? end + def merge_spec_dependables(dependables) + # We have a hash of specs names (stable/head) to dependency lists. + # Merge all of the dependency lists together, removing any duplicates. + all_dependables = [].union(*dependables.values.map(&:to_a)) + + all_dependables.map do |dependable| + { + dependable: dependable, + # Now find the list of specs each dependency was a part of. + specs: dependables.map { |spec, spec_deps| spec if spec_deps&.include?(dependable) }.compact, + } + end + end + private :merge_spec_dependables + # @private def to_hash - dependencies = deps + # Create a hash of spec names (stable/head) to the list of dependencies under each + dependencies = self.class.spec_syms.to_h do |sym| + [sym, send(sym)&.declared_deps] + end + dependencies.transform_values! { |deps| deps&.reject(&:implicit?) } # Remove all implicit deps from all lists + requirements = self.class.spec_syms.to_h do |sym| + [sym, send(sym)&.requirements] + end hsh = { "name" => name, @@ -2138,25 +2164,13 @@ class Formula "keg_only" => keg_only?, "keg_only_reason" => keg_only_reason&.to_hash, "options" => [], - "build_dependencies" => dependencies.select(&:build?) - .map(&:name) - .uniq, - "dependencies" => dependencies.reject(&:optional?) - .reject(&:recommended?) - .reject(&:build?) - .reject(&:test?) - .map(&:name) - .uniq, - "test_dependencies" => dependencies.select(&:test?) - .map(&:name) - .uniq, - "recommended_dependencies" => dependencies.select(&:recommended?) - .map(&:name) - .uniq, - "optional_dependencies" => dependencies.select(&:optional?) - .map(&:name) - .uniq, - "uses_from_macos" => uses_from_macos_elements.uniq, + "build_dependencies" => [], + "dependencies" => [], + "test_dependencies" => [], + "recommended_dependencies" => [], + "optional_dependencies" => [], + "uses_from_macos" => [], + "uses_from_macos_bounds" => [], "requirements" => [], "conflicts_with" => conflicts.map(&:name), "conflicts_with_reasons" => conflicts.map(&:reason), @@ -2201,7 +2215,56 @@ class Formula { "option" => opt.flag, "description" => opt.description } end - hsh["requirements"] = requirements.map do |req| + dependencies.each do |spec_sym, spec_deps| + next if spec_deps.nil? + + dep_hash = if spec_sym == :stable + hsh + else + next if spec_deps == dependencies[:stable] + + hsh["#{spec_sym}_dependencies"] ||= {} + end + + dep_hash["build_dependencies"] = spec_deps.select(&:build?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + dep_hash["dependencies"] = spec_deps.reject(&:optional?) + .reject(&:recommended?) + .reject(&:build?) + .reject(&:test?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + dep_hash["test_dependencies"] = spec_deps.select(&:test?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + dep_hash["recommended_dependencies"] = spec_deps.select(&:recommended?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + dep_hash["optional_dependencies"] = spec_deps.select(&:optional?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + + uses_from_macos_deps = spec_deps.select(&:uses_from_macos?).uniq + dep_hash["uses_from_macos"] = uses_from_macos_deps.map do |dep| + if dep.tags.length >= 2 + { dep.name => dep.tags } + elsif dep.tags.present? + { dep.name => dep.tags.first } + else + dep.name + end + end + dep_hash["uses_from_macos_bounds"] = uses_from_macos_deps.map(&:bounds) + end + + hsh["requirements"] = merge_spec_dependables(requirements).map do |data| + req = data[:dependable] req_name = req.name.dup req_name.prepend("maximum_") if req.try(:comparator) == "<=" { @@ -2210,6 +2273,7 @@ class Formula "download" => req.download, "version" => req.try(:version) || req.try(:arch), "contexts" => req.tags, + "specs" => data[:specs], } end @@ -2909,10 +2973,17 @@ class Formula #
version_scheme 1
attr_rw :version_scheme + # @private + def spec_syms + [:stable, :head].freeze + end + # A list of the {.stable} and {.head} {SoftwareSpec}s. # @private def specs - [stable, head].freeze + spec_syms.map do |sym| + send(sym) + end.freeze end # @!attribute [w] url diff --git a/Library/Homebrew/formula_auditor.rb b/Library/Homebrew/formula_auditor.rb index 7b58aea88b..9a3fc6a17e 100644 --- a/Library/Homebrew/formula_auditor.rb +++ b/Library/Homebrew/formula_auditor.rb @@ -254,7 +254,7 @@ module Homebrew @specs.each do |spec| # Check for things we don't like to depend on. # We allow non-Homebrew installs whenever possible. - spec.deps.each do |dep| + spec.declared_deps.each do |dep| begin dep_f = dep.to_formula rescue TapFormulaUnavailableError @@ -323,7 +323,7 @@ module Homebrew end # we want to allow uses_from_macos for aliases but not bare dependencies - if self.class.aliases.include?(dep.name) && spec.uses_from_macos_names.exclude?(dep.name) + if self.class.aliases.include?(dep.name) && !dep.uses_from_macos? problem "Dependency '#{dep.name}' is an alias; use the canonical name '#{dep.to_formula.full_name}'." end diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 20a53bd0c7..89255b0bda 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -156,6 +156,79 @@ module Formulary dep.keys.first end + requirements = {} + json_formula["requirements"].map do |req| + req_name = req["name"].to_sym + next if API_SUPPORTED_REQUIREMENTS.exclude?(req_name) + + req_version = case req_name + when :arch + req["version"]&.to_sym + when :macos, :maximum_macos + MacOSVersion::SYMBOLS.key(req["version"]) + else + req["version"] + end + + req_tags = [] + req_tags << req_version if req_version.present? + req_tags += req["contexts"].map do |tag| + case tag + when String + tag.to_sym + when Hash + tag.deep_transform_keys(&:to_sym) + else + tag + end + end + + spec_hash = req_tags.empty? ? req_name : { req_name => req_tags } + + specs = req["specs"] + specs ||= ["stable", "head"] # backwards compatibility + specs.each do |spec| + requirements[spec.to_sym] ||= [] + requirements[spec.to_sym] << spec_hash + end + end + + add_deps = lambda do |spec| + T.bind(self, SoftwareSpec) + + dep_json = json_formula.fetch("#{spec}_dependencies", json_formula) + + dep_json["dependencies"].each do |dep| + # Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux + next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) && + !Homebrew::SimulateSystem.simulating_or_running_on_macos? + + depends_on dep + end + + [:build, :test, :recommended, :optional].each do |type| + dep_json["#{type}_dependencies"].each do |dep| + # Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux + next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) && + !Homebrew::SimulateSystem.simulating_or_running_on_macos? + + depends_on dep => type + end + end + + dep_json["uses_from_macos"].each_with_index do |dep, index| + bounds = dep_json.fetch("uses_from_macos_bounds", [])[index] || {} + bounds.deep_transform_keys!(&:to_sym) + bounds.deep_transform_values! { |val| val.is_a?(String) ? val.to_sym : val } + + if dep.is_a?(Hash) + uses_from_macos dep.deep_transform_values(&:to_sym).merge(bounds) + else + uses_from_macos dep, bounds + end + end + end + klass = Class.new(::Formula) do @loaded_from_api = true @@ -171,11 +244,26 @@ module Formulary url urls_stable["url"], **url_spec version json_formula["versions"]["stable"] sha256 urls_stable["checksum"] if urls_stable["checksum"].present? + + instance_exec(:stable, &add_deps) + + requirements[:stable]&.each do |req| + depends_on req + end end end if (urls_head = json_formula["urls"]["head"].presence) - head urls_head["url"], branch: urls_head["branch"] + head do + url_spec = { branch: urls_head["branch"] }.compact + url urls_head["url"], **url_spec + + instance_exec(:head, &add_deps) + + requirements[:head]&.each do |req| + depends_on req + end + end end if (bottles_stable = json_formula["bottle"]["stable"].presence) @@ -208,54 +296,6 @@ module Formulary disable! date: disable_date, because: reason end - json_formula["dependencies"].each do |dep| - next if uses_from_macos_names.include?(dep) && !Homebrew::SimulateSystem.simulating_or_running_on_macos? - - depends_on dep - end - - [:build, :test, :recommended, :optional].each do |type| - json_formula["#{type}_dependencies"].each do |dep| - next if uses_from_macos_names.include?(dep) && !Homebrew::SimulateSystem.simulating_or_running_on_macos? - - depends_on dep => type - end - end - - json_formula["uses_from_macos"].each do |dep| - dep = dep.deep_transform_values(&:to_sym) if dep.is_a?(Hash) - uses_from_macos dep - end - - json_formula["requirements"].each do |req| - req_name = req["name"].to_sym - next if API_SUPPORTED_REQUIREMENTS.exclude?(req_name) - - req_version = case req_name - when :arch - req["version"]&.to_sym - when :macos, :maximum_macos - MacOSVersion::SYMBOLS.key(req["version"]) - else - req["version"] - end - - req_tags = [] - req_tags << req_version if req_version.present? - req_tags += req["contexts"].map do |tag| - case tag - when String - tag.to_sym - when Hash - tag.deep_transform_keys(&:to_sym) - else - tag - end - end - - depends_on req_name => req_tags - end - json_formula["conflicts_with"].each_with_index do |conflict, index| conflicts_with conflict, because: json_formula.dig("conflicts_with_reasons", index) end diff --git a/Library/Homebrew/language/perl.rb b/Library/Homebrew/language/perl.rb index f4822239ad..c9b1775cef 100644 --- a/Library/Homebrew/language/perl.rb +++ b/Library/Homebrew/language/perl.rb @@ -11,10 +11,13 @@ module Language module_function def detected_perl_shebang(formula = self) - perl_path = if formula.deps.map(&:name).include? "perl" - Formula["perl"].opt_bin/"perl" - elsif formula.uses_from_macos_names.include? "perl" - "/usr/bin/perl#{MacOS.preferred_perl_version}" + perl_deps = formula.declared_deps.select { |dep| dep.name == "perl" } + perl_path = if perl_deps.present? + if perl_deps.any? { |dep| !dep.uses_from_macos? || !dep.use_macos_install? } + Formula["perl"].opt_bin/"perl" + else + "/usr/bin/perl#{MacOS.preferred_perl_version}" + end else raise ShebangDetectionError.new("Perl", "formula does not depend on Perl") end diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index b6474b857a..6182838ea4 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -24,8 +24,7 @@ class SoftwareSpec }.freeze attr_reader :name, :full_name, :owner, :build, :resources, :patches, :options, :deprecated_flags, - :deprecated_options, :dependency_collector, :bottle_specification, :compiler_failures, - :uses_from_macos_elements + :deprecated_options, :dependency_collector, :bottle_specification, :compiler_failures def_delegators :@resource, :stage, :fetch, :verify_download_integrity, :source_modified_time, :download_name, :cached_download, :clear_cache, :checksum, :mirrors, :specs, :using, :version, :mirror, @@ -195,28 +194,34 @@ class SoftwareSpec deps = [bounds.shift].to_h end + spec, tags = deps.is_a?(Hash) ? deps.first : deps + raise TypeError, "Dependency name must be a string!" unless spec.is_a?(String) + @uses_from_macos_elements << deps - # Check whether macOS is new enough for dependency to not be required. - if Homebrew::SimulateSystem.simulating_or_running_on_macos? - # Assume the oldest macOS version when simulating a generic macOS version - return if Homebrew::SimulateSystem.current_os == :macos && !bounds.key?(:since) - - if Homebrew::SimulateSystem.current_os != :macos - current_os = MacOSVersion.from_symbol(Homebrew::SimulateSystem.current_os) - since_os = MacOSVersion.from_symbol(bounds[:since]) if bounds.key?(:since) - return if current_os >= since_os - end - end - - depends_on deps + depends_on UsesFromMacOSDependency.new(spec, Array(tags), bounds: bounds) end + # @deprecated + # rubocop:disable Style/TrivialAccessors + def uses_from_macos_elements + # TODO: remove all @uses_from_macos_elements when disabling or removing this method + # odeprecated "#uses_from_macos_elements", "#declared_deps" + @uses_from_macos_elements + end + # rubocop:enable Style/TrivialAccessors + + # @deprecated def uses_from_macos_names + # odeprecated "#uses_from_macos_names", "#declared_deps" uses_from_macos_elements.flat_map { |e| e.is_a?(Hash) ? e.keys : e } end def deps + dependency_collector.deps.dup_without_system_deps + end + + def declared_deps dependency_collector.deps end diff --git a/Library/Homebrew/test/dependency_collector_spec.rb b/Library/Homebrew/test/dependency_collector_spec.rb index 672812277c..1af0d4f05f 100644 --- a/Library/Homebrew/test/dependency_collector_spec.rb +++ b/Library/Homebrew/test/dependency_collector_spec.rb @@ -52,13 +52,13 @@ describe DependencyCollector do it "creates a resource dependency from a CVS URL" do resource = Resource.new resource.url(":pserver:anonymous:@brew.sh:/cvsroot/foo/bar", using: :cvs) - expect(collector.add(resource)).to eq(Dependency.new("cvs", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("cvs", [:build, :test, :implicit])) end it "creates a resource dependency from a '.7z' URL" do resource = Resource.new resource.url("https://brew.sh/foo.7z") - expect(collector.add(resource)).to eq(Dependency.new("p7zip", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("p7zip", [:build, :test, :implicit])) end it "creates a resource dependency from a '.gz' URL" do @@ -70,25 +70,25 @@ describe DependencyCollector do it "creates a resource dependency from a '.lz' URL" do resource = Resource.new resource.url("https://brew.sh/foo.lz") - expect(collector.add(resource)).to eq(Dependency.new("lzip", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("lzip", [:build, :test, :implicit])) end it "creates a resource dependency from a '.lha' URL" do resource = Resource.new resource.url("https://brew.sh/foo.lha") - expect(collector.add(resource)).to eq(Dependency.new("lha", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("lha", [:build, :test, :implicit])) end it "creates a resource dependency from a '.lzh' URL" do resource = Resource.new resource.url("https://brew.sh/foo.lzh") - expect(collector.add(resource)).to eq(Dependency.new("lha", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("lha", [:build, :test, :implicit])) end it "creates a resource dependency from a '.rar' URL" do resource = Resource.new resource.url("https://brew.sh/foo.rar") - expect(collector.add(resource)).to eq(Dependency.new("libarchive", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("libarchive", [:build, :test, :implicit])) end it "raises a TypeError for unknown classes" do diff --git a/Library/Homebrew/test/dependency_spec.rb b/Library/Homebrew/test/dependency_spec.rb index 431006cb57..b9367544c2 100644 --- a/Library/Homebrew/test/dependency_spec.rb +++ b/Library/Homebrew/test/dependency_spec.rb @@ -100,15 +100,20 @@ describe Dependency do expect(foo1).not_to eql(foo3) end - describe TapDependency do - subject(:dependency) { described_class.new("foo/bar/dog") } - - specify "#tap" do + describe "#tap" do + it "returns a tap passed a fully-qualified name" do + dependency = described_class.new("foo/bar/dog") expect(dependency.tap).to eq(Tap.new("foo", "bar")) end - specify "#option_names" do - expect(dependency.option_names).to eq(%w[dog]) + it "returns no tap passed a simple name" do + dependency = described_class.new("dog") + expect(dependency.tap).to be_nil end end + + specify "#option_names" do + dependency = described_class.new("foo/bar/dog") + expect(dependency.option_names).to eq(%w[dog]) + end end diff --git a/Library/Homebrew/test/formulary_spec.rb b/Library/Homebrew/test/formulary_spec.rb index 35865147b6..89a627b950 100644 --- a/Library/Homebrew/test/formulary_spec.rb +++ b/Library/Homebrew/test/formulary_spec.rb @@ -340,6 +340,7 @@ describe Formulary do expect(formula).to be_a(Formula) expect(formula.keg_only_reason.reason).to eq :provided_by_macos + expect(formula.declared_deps.count).to eq 6 if OS.mac? expect(formula.deps.count).to eq 5 else @@ -398,6 +399,7 @@ describe Formulary do formula = described_class.factory(formula_name) expect(formula).to be_a(Formula) + expect(formula.declared_deps.count).to eq 7 expect(formula.deps.count).to eq 6 expect(formula.deps.map(&:name).include?("variations_dep")).to be true expect(formula.deps.map(&:name).include?("uses_from_macos_dep")).to be false @@ -409,6 +411,7 @@ describe Formulary do formula = described_class.factory(formula_name) expect(formula).to be_a(Formula) + expect(formula.declared_deps.count).to eq 6 expect(formula.deps.count).to eq 6 expect(formula.deps.map(&:name).include?("uses_from_macos_dep")).to be true end @@ -419,6 +422,7 @@ describe Formulary do formula = described_class.factory(formula_name) expect(formula).to be_a(Formula) + expect(formula.declared_deps.count).to eq 6 expect(formula.deps.count).to eq 5 expect(formula.deps.map(&:name).include?("uses_from_macos_dep")).to be true end diff --git a/Library/Homebrew/test/os/linux/dependency_collector_spec.rb b/Library/Homebrew/test/os/linux/dependency_collector_spec.rb index b213e77e6c..15a149abe4 100644 --- a/Library/Homebrew/test/os/linux/dependency_collector_spec.rb +++ b/Library/Homebrew/test/os/linux/dependency_collector_spec.rb @@ -14,19 +14,19 @@ describe DependencyCollector do it "creates a resource dependency from a '.xz' URL" do resource.url("https://brew.sh/foo.xz") allow_any_instance_of(Object).to receive(:which).with("xz") - expect(collector.add(resource)).to eq(Dependency.new("xz", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("xz", [:build, :test, :implicit])) end it "creates a resource dependency from a '.zip' URL" do resource.url("https://brew.sh/foo.zip") allow_any_instance_of(Object).to receive(:which).with("unzip") - expect(collector.add(resource)).to eq(Dependency.new("unzip", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("unzip", [:build, :test, :implicit])) end it "creates a resource dependency from a '.bz2' URL" do resource.url("https://brew.sh/foo.tar.bz2") allow_any_instance_of(Object).to receive(:which).with("bzip2") - expect(collector.add(resource)).to eq(Dependency.new("bzip2", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("bzip2", [:build, :test, :implicit])) end end diff --git a/Library/Homebrew/test/os/mac/dependency_collector_spec.rb b/Library/Homebrew/test/os/mac/dependency_collector_spec.rb index fe0af3a0a4..db369ac998 100644 --- a/Library/Homebrew/test/os/mac/dependency_collector_spec.rb +++ b/Library/Homebrew/test/os/mac/dependency_collector_spec.rb @@ -34,6 +34,6 @@ describe DependencyCollector do specify "Resource dependency from a Subversion URL" do resource = Resource.new resource.url("svn://brew.sh/foo/bar") - expect(collector.add(resource)).to eq(Dependency.new("subversion", [:build, :test])) + expect(collector.add(resource)).to eq(Dependency.new("subversion", [:build, :test, :implicit])) end end diff --git a/Library/Homebrew/test/os/mac/formula_spec.rb b/Library/Homebrew/test/os/mac/formula_spec.rb index 8a91e72e4a..a188ee4269 100644 --- a/Library/Homebrew/test/os/mac/formula_spec.rb +++ b/Library/Homebrew/test/os/mac/formula_spec.rb @@ -19,19 +19,27 @@ describe Formula do expect(f.class.stable.deps).to be_empty expect(f.class.head.deps).to be_empty + expect(f.class.stable.declared_deps).not_to be_empty + expect(f.class.head.declared_deps).not_to be_empty + expect(f.class.stable.declared_deps.first.name).to eq("foo") + expect(f.class.head.declared_deps.first.name).to eq("foo") expect(f.class.stable.uses_from_macos_elements.first).to eq("foo") expect(f.class.head.uses_from_macos_elements.first).to eq("foo") end - it "doesn't add a macOS dependency to any spec if the OS version doesn't meet requirements" do + it "adds a dependency to any spec if the OS version doesn't meet requirements" do f = formula "foo" do url "foo-1.0" uses_from_macos("foo", since: :high_sierra) end + expect(f.class.stable.deps).not_to be_empty + expect(f.class.head.deps).not_to be_empty expect(f.class.stable.deps.first.name).to eq("foo") expect(f.class.head.deps.first.name).to eq("foo") + expect(f.class.stable.declared_deps).not_to be_empty + expect(f.class.head.declared_deps).not_to be_empty expect(f.class.stable.uses_from_macos_elements).to eq(["foo"]) expect(f.class.head.uses_from_macos_elements).to eq(["foo"]) end diff --git a/Library/Homebrew/test/software_spec_spec.rb b/Library/Homebrew/test/software_spec_spec.rb index dc93aa2ac6..458248468a 100644 --- a/Library/Homebrew/test/software_spec_spec.rb +++ b/Library/Homebrew/test/software_spec_spec.rb @@ -139,37 +139,59 @@ describe SoftwareSpec do it "allows specifying dependencies" do spec.uses_from_macos("foo") + expect(spec.declared_deps).not_to be_empty + expect(spec.deps).not_to be_empty expect(spec.deps.first.name).to eq("foo") + expect(spec.deps.first).to be_uses_from_macos + expect(spec.deps.first).not_to be_use_macos_install end it "works with tags" do spec.uses_from_macos("foo" => :build) + expect(spec.declared_deps).not_to be_empty + expect(spec.deps).not_to be_empty expect(spec.deps.first.name).to eq("foo") expect(spec.deps.first.tags).to include(:build) + expect(spec.deps.first).to be_uses_from_macos + expect(spec.deps.first).not_to be_use_macos_install end - it "ignores dependencies with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do + it "handles dependencies with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1" spec.uses_from_macos("foo") expect(spec.deps).to be_empty + expect(spec.declared_deps.first.name).to eq("foo") + expect(spec.declared_deps.first.tags).to be_empty + expect(spec.declared_deps.first).to be_uses_from_macos + expect(spec.declared_deps.first).to be_use_macos_install end - it "ignores dependencies with tags with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do + it "handles dependencies with tags with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1" spec.uses_from_macos("foo" => :build) expect(spec.deps).to be_empty + expect(spec.declared_deps.first.name).to eq("foo") + expect(spec.declared_deps.first.tags).to include(:build) + expect(spec.declared_deps.first).to be_uses_from_macos + expect(spec.declared_deps.first).to be_use_macos_install end it "ignores OS version specifications" do spec.uses_from_macos("foo", since: :mojave) spec.uses_from_macos("bar" => :build, :since => :mojave) + expect(spec.deps.count).to eq 2 expect(spec.deps.first.name).to eq("foo") + expect(spec.deps.first).to be_uses_from_macos + expect(spec.deps.first).not_to be_use_macos_install expect(spec.deps.last.name).to eq("bar") expect(spec.deps.last.tags).to include(:build) + expect(spec.deps.last).to be_uses_from_macos + expect(spec.deps.last).not_to be_use_macos_install + expect(spec.declared_deps.count).to eq 2 end end @@ -183,30 +205,54 @@ describe SoftwareSpec do spec.uses_from_macos("foo", since: :el_capitan) expect(spec.deps).to be_empty + expect(spec.declared_deps).not_to be_empty + expect(spec.declared_deps.first).to be_uses_from_macos + expect(spec.declared_deps.first).to be_use_macos_install expect(spec.uses_from_macos_elements.first).to eq("foo") end - it "doesn't add a macOS dependency if the OS version doesn't meet requirements" do + it "add a macOS dependency if the OS version doesn't meet requirements" do spec.uses_from_macos("foo", since: :high_sierra) + expect(spec.declared_deps).not_to be_empty + expect(spec.deps).not_to be_empty expect(spec.deps.first.name).to eq("foo") + expect(spec.deps.first).to be_uses_from_macos + expect(spec.deps.first).not_to be_use_macos_install expect(spec.uses_from_macos_elements).to eq(["foo"]) end it "works with tags" do spec.uses_from_macos("foo" => :build, :since => :high_sierra) + expect(spec.declared_deps).not_to be_empty + expect(spec.deps).not_to be_empty + dep = spec.deps.first expect(dep.name).to eq("foo") expect(dep.tags).to include(:build) + expect(dep.first).to be_uses_from_macos + expect(dep.first).not_to be_use_macos_install end - it "doesn't add a dependency if no OS version is specified" do + it "doesn't add an effective dependency if no OS version is specified" do spec.uses_from_macos("foo") spec.uses_from_macos("bar" => :build) expect(spec.deps).to be_empty + expect(spec.declared_deps).not_to be_empty + + dep = spec.declared_deps.first + expect(dep.name).to eq("foo") + expect(dep.first).to be_uses_from_macos + expect(dep.first).to be_use_macos_install + + dep = spec.declared_deps.last + expect(dep.name).to eq("bar") + expect(dep.tags).to include(:build) + expect(dep.first).to be_uses_from_macos + expect(dep.first).to be_use_macos_install end it "raises an error if passing invalid OS versions" do