 45978435e7
			
		
	
	
		45978435e7
		
			
		
	
	
	
	
		
			
			- Previously I thought that comments were fine to discourage people from wasting their time trying to bump things that used `undef` that Sorbet didn't support. But RuboCop is better at this since it'll complain if the comments are unnecessary. - Suggested in https://github.com/Homebrew/brew/pull/18018#issuecomment-2283369501. - I've gone for a mixture of `rubocop:disable` for the files that can't be `typed: strict` (use of undef, required before everything else, etc) and `rubocop:todo` for everything else that should be tried to make strictly typed. There's no functional difference between the two as `rubocop:todo` is `rubocop:disable` with a different name. - And I entirely disabled the cop for the docs/ directory since `typed: strict` isn't going to gain us anything for some Markdown linting config files. - This means that now it's easier to track what needs to be done rather than relying on checklists of files in our big Sorbet issue: ```shell $ git grep 'typed: true # rubocop:todo Sorbet/StrictSigil' | wc -l 268 ``` - And this is confirmed working for new files: ```shell $ git status On branch use-rubocop-for-sorbet-strict-sigils Untracked files: (use "git add <file>..." to include in what will be committed) Library/Homebrew/bad.rb Library/Homebrew/good.rb nothing added to commit but untracked files present (use "git add" to track) $ brew style Offenses: bad.rb:1:1: C: Sorbet/StrictSigil: Sorbet sigil should be at least strict got true. ^^^^^^^^^^^^^ 1340 files inspected, 1 offense detected ```
		
			
				
	
	
		
			226 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # typed: true # rubocop:todo Sorbet/StrictSigil
 | |
| # frozen_string_literal: true
 | |
| 
 | |
| require "dependency"
 | |
| require "dependencies"
 | |
| require "requirement"
 | |
| require "requirements"
 | |
| require "extend/cachable"
 | |
| 
 | |
| # A dependency is a formula that another formula needs to install.
 | |
| # A requirement is something other than a formula that another formula
 | |
| # needs to be present. This includes external language modules,
 | |
| # command-line tools in the path, or any arbitrary predicate.
 | |
| #
 | |
| # The `depends_on` method in the formula DSL is used to declare
 | |
| # dependencies and requirements.
 | |
| 
 | |
| # This class is used by `depends_on` in the formula DSL to turn dependency
 | |
| # specifications into the proper kinds of dependencies and requirements.
 | |
| class DependencyCollector
 | |
|   extend Cachable
 | |
| 
 | |
|   attr_reader :deps, :requirements
 | |
| 
 | |
|   sig { void }
 | |
|   def initialize
 | |
|     # Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
 | |
|     @deps = Dependencies.new
 | |
|     @requirements = Requirements.new
 | |
| 
 | |
|     init_global_dep_tree_if_needed!
 | |
|   end
 | |
| 
 | |
|   def initialize_dup(other)
 | |
|     super
 | |
|     @deps = @deps.dup
 | |
|     @requirements = @requirements.dup
 | |
|   end
 | |
| 
 | |
|   def freeze
 | |
|     @deps.freeze
 | |
|     @requirements.freeze
 | |
|     super
 | |
|   end
 | |
| 
 | |
|   def add(spec)
 | |
|     case dep = fetch(spec)
 | |
|     when Array
 | |
|       dep.compact.each { |dep| @deps << dep }
 | |
|     when Dependency
 | |
|       @deps << dep
 | |
|     when Requirement
 | |
|       @requirements << dep
 | |
|     when nil
 | |
|       # no-op when we have a nil value
 | |
|       nil
 | |
|     else
 | |
|       raise ArgumentError, "DependencyCollector#add passed something that isn't a Dependency or Requirement!"
 | |
|     end
 | |
|     dep
 | |
|   end
 | |
| 
 | |
|   def fetch(spec)
 | |
|     self.class.cache.fetch(cache_key(spec)) { |key| self.class.cache[key] = build(spec) }
 | |
|   end
 | |
| 
 | |
|   def cache_key(spec)
 | |
|     if spec.is_a?(Resource)
 | |
|       if spec.download_strategy <= CurlDownloadStrategy
 | |
|         return "#{spec.download_strategy}#{File.extname(spec.url).split("?").first}"
 | |
|       end
 | |
| 
 | |
|       return spec.download_strategy
 | |
|     end
 | |
|     spec
 | |
|   end
 | |
| 
 | |
|   def build(spec)
 | |
|     spec, tags = spec.is_a?(Hash) ? spec.first : spec
 | |
|     parse_spec(spec, Array(tags))
 | |
|   end
 | |
| 
 | |
|   sig { params(related_formula_names: T::Array[String]).returns(T.nilable(Dependency)) }
 | |
|   def gcc_dep_if_needed(related_formula_names); end
 | |
| 
 | |
|   sig { params(related_formula_names: T::Array[String]).returns(T.nilable(Dependency)) }
 | |
|   def glibc_dep_if_needed(related_formula_names); end
 | |
| 
 | |
|   def git_dep_if_needed(tags)
 | |
|     require "utils/git"
 | |
|     return if Utils::Git.available?
 | |
| 
 | |
|     Dependency.new("git", [*tags, :implicit])
 | |
|   end
 | |
| 
 | |
|   def curl_dep_if_needed(tags)
 | |
|     Dependency.new("curl", [*tags, :implicit])
 | |
|   end
 | |
| 
 | |
|   def subversion_dep_if_needed(tags)
 | |
|     require "utils/svn"
 | |
|     return if Utils::Svn.available?
 | |
| 
 | |
|     Dependency.new("subversion", [*tags, :implicit])
 | |
|   end
 | |
| 
 | |
|   def cvs_dep_if_needed(tags)
 | |
|     Dependency.new("cvs", [*tags, :implicit]) unless which("cvs")
 | |
|   end
 | |
| 
 | |
|   def xz_dep_if_needed(tags)
 | |
|     Dependency.new("xz", [*tags, :implicit]) unless which("xz")
 | |
|   end
 | |
| 
 | |
|   def zstd_dep_if_needed(tags)
 | |
|     Dependency.new("zstd", [*tags, :implicit]) unless which("zstd")
 | |
|   end
 | |
| 
 | |
|   def unzip_dep_if_needed(tags)
 | |
|     Dependency.new("unzip", [*tags, :implicit]) unless which("unzip")
 | |
|   end
 | |
| 
 | |
|   def bzip2_dep_if_needed(tags)
 | |
|     Dependency.new("bzip2", [*tags, :implicit]) unless which("bzip2")
 | |
|   end
 | |
| 
 | |
|   def self.tar_needs_xz_dependency?
 | |
|     !new.xz_dep_if_needed([]).nil?
 | |
|   end
 | |
| 
 | |
|   private
 | |
| 
 | |
|   sig { void }
 | |
|   def init_global_dep_tree_if_needed!; end
 | |
| 
 | |
|   sig {
 | |
|     params(spec: T.any(String, Resource, Symbol, Requirement, Dependency, Class),
 | |
|            tags: T::Array[Symbol]).returns(T.any(Dependency, Requirement, Array, NilClass))
 | |
|   }
 | |
|   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)
 | |
|     when Resource
 | |
|       resource_dep(spec, tags)
 | |
|     when Symbol
 | |
|       parse_symbol_spec(spec, tags)
 | |
|     when Requirement, Dependency
 | |
|       spec
 | |
|     when Class
 | |
|       parse_class_spec(spec, tags)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def parse_string_spec(spec, tags)
 | |
|     Dependency.new(spec, tags)
 | |
|   end
 | |
| 
 | |
|   def parse_symbol_spec(spec, tags)
 | |
|     # When modifying this list of supported requirements, consider
 | |
|     # whether `Formulary::API_SUPPORTED_REQUIREMENTS` should also be changed.
 | |
|     case spec
 | |
|     when :arch          then ArchRequirement.new(tags)
 | |
|     when :codesign      then CodesignRequirement.new(tags)
 | |
|     when :linux         then LinuxRequirement.new(tags)
 | |
|     when :macos         then MacOSRequirement.new(tags)
 | |
|     when :maximum_macos then MacOSRequirement.new(tags, comparator: "<=")
 | |
|     when :xcode         then XcodeRequirement.new(tags)
 | |
|     else
 | |
|       raise ArgumentError, "Unsupported special dependency #{spec.inspect}"
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def parse_class_spec(spec, tags)
 | |
|     raise TypeError, "#{spec.inspect} is not a Requirement subclass" unless spec < Requirement
 | |
| 
 | |
|     spec.new(tags)
 | |
|   end
 | |
| 
 | |
|   def resource_dep(spec, tags)
 | |
|     tags << :build << :test
 | |
|     strategy = spec.download_strategy
 | |
| 
 | |
|     if strategy <= HomebrewCurlDownloadStrategy
 | |
|       [curl_dep_if_needed(tags), parse_url_spec(spec.url, tags)]
 | |
|     elsif strategy <= NoUnzipCurlDownloadStrategy
 | |
|       # ensure NoUnzip never adds any dependencies
 | |
|     elsif strategy <= CurlDownloadStrategy
 | |
|       parse_url_spec(spec.url, tags)
 | |
|     elsif strategy <= GitDownloadStrategy
 | |
|       git_dep_if_needed(tags)
 | |
|     elsif strategy <= SubversionDownloadStrategy
 | |
|       subversion_dep_if_needed(tags)
 | |
|     elsif strategy <= MercurialDownloadStrategy
 | |
|       Dependency.new("mercurial", [*tags, :implicit])
 | |
|     elsif strategy <= FossilDownloadStrategy
 | |
|       Dependency.new("fossil", [*tags, :implicit])
 | |
|     elsif strategy <= BazaarDownloadStrategy
 | |
|       Dependency.new("breezy", [*tags, :implicit])
 | |
|     elsif strategy <= CVSDownloadStrategy
 | |
|       cvs_dep_if_needed(tags)
 | |
|     elsif strategy < AbstractDownloadStrategy
 | |
|       # allow unknown strategies to pass through
 | |
|     else
 | |
|       raise TypeError, "#{strategy.inspect} is not an AbstractDownloadStrategy subclass"
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def parse_url_spec(url, tags)
 | |
|     case File.extname(url)
 | |
|     when ".xz"          then xz_dep_if_needed(tags)
 | |
|     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, :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
 | |
| 
 | |
| require "extend/os/dependency_collector"
 |