diff --git a/Library/Homebrew/build_environment.rb b/Library/Homebrew/build_environment.rb index d63604faa9..9b03f709aa 100644 --- a/Library/Homebrew/build_environment.rb +++ b/Library/Homebrew/build_environment.rb @@ -33,9 +33,17 @@ class BuildEnvironment module DSL extend T::Sig + # Initialise @env for each class which may use this DSL (e.g. each formula subclass). + # `env` may never be called, and it needs to be initialised before the class is frozen. + def inherited(child) + super + child.instance_eval do + @env = BuildEnvironment.new + end + end + sig { params(settings: Symbol).returns(BuildEnvironment) } def env(*settings) - @env ||= BuildEnvironment.new @env.merge(settings) end end diff --git a/Library/Homebrew/dependency_collector.rb b/Library/Homebrew/dependency_collector.rb index c1f4085b9d..04dddf24b1 100644 --- a/Library/Homebrew/dependency_collector.rb +++ b/Library/Homebrew/dependency_collector.rb @@ -26,18 +26,25 @@ class DependencyCollector 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_copy(other) + 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 Dependency diff --git a/Library/Homebrew/extend/module.rb b/Library/Homebrew/extend/module.rb index 91264a1498..b09433b483 100644 --- a/Library/Homebrew/extend/module.rb +++ b/Library/Homebrew/extend/module.rb @@ -5,11 +5,17 @@ class Module def attr_rw(*attrs) attrs.each do |attr| module_eval <<-EOS, __FILE__, __LINE__+1 - def #{attr}(val=nil) # def prefix(val=nil) - @#{attr} ||= nil # @prefix ||= nil - return @#{attr} if val.nil? # return @prefix if val.nil? - @#{attr} = val # @prefix = val - end # end + def #{attr}(val=nil) # def prefix(val=nil) + if val.nil? # if val.nil? + if instance_variable_defined?(:@#{attr}) # if instance_variable_defined?(:@prefix) + return @#{attr} # return @prefix + else # else + return nil # return nil + end # end + end # end + # + @#{attr} = val # @prefix = val + end # end EOS end end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index b46458de22..aedb036b8c 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -184,6 +184,11 @@ class Formula # Only allow instances of subclasses. The base class does not hold any spec information (URLs etc). raise "Do not call `Formula.new' directly without a subclass." unless self.class < Formula + # Stop any subsequent modification of a formula's definition. + # Changes do not propagate to existing instances of formulae. + # Now that we have an instance, it's too late to make any changes to the class-level definition. + self.class.freeze + @name = name @path = path @alias_path = alias_path @@ -2699,6 +2704,22 @@ class Formula include BuildEnvironment::DSL include OnSystem::MacOSAndLinux + # Initialise instance variables for each subclass. These need to be initialised before the class is frozen, + # and some DSL may never be called so it can't be done lazily. + def inherited(child) + super + child.instance_eval do + # Ensure this is synced with `freeze` + @stable = SoftwareSpec.new(flags: build_flags) + @head = HeadSoftwareSpec.new(flags: build_flags) + @livecheck = Livecheck.new(self) + @conflicts = [] + @skip_clean_paths = Set.new + @link_overwrite_paths = Set.new + @allowed_missing_libraries = Set.new + end + end + def method_added(method) super @@ -2710,6 +2731,16 @@ class Formula end end + def freeze + specs.each(&:freeze) + @livecheck.freeze + @conflicts.freeze + @skip_clean_paths.freeze + @link_overwrite_paths.freeze + @allowed_missing_libraries.freeze + super + end + # Whether this formula contains OS/arch-specific blocks # (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`). # @private @@ -2790,6 +2821,18 @@ class Formula # @private attr_reader :plist_manual + # @private + attr_reader :conflicts + + # @private + attr_reader :skip_clean_paths + + # @private + attr_reader :link_overwrite_paths + + # @private + attr_reader :allowed_missing_libraries + # If `pour_bottle?` returns `false` the user-visible reason to display for # why they cannot use the bottle. # @private @@ -2820,7 +2863,7 @@ class Formula # A list of the {.stable} and {.head} {SoftwareSpec}s. # @private def specs - @specs ||= [stable, head].freeze + [stable, head].freeze end # @!attribute [w] url @@ -2937,7 +2980,6 @@ class Formula # depends_on "libffi" # end def stable(&block) - @stable ||= SoftwareSpec.new(flags: build_flags) return @stable unless block @stable.instance_eval(&block) @@ -2956,7 +2998,6 @@ class Formula # or (if autodetect fails): #
head "https://hg.is.awesome.but.git.has.won.example.com/", using: :hg
def head(val = nil, specs = {}, &block) - @head ||= HeadSoftwareSpec.new(flags: build_flags) if block @head.instance_eval(&block) elsif val @@ -3107,11 +3148,6 @@ class Formula @plist_manual = options[:manual] end - # @private - def conflicts - @conflicts ||= [] - end - # One or more formulae that conflict with this one and why. #
conflicts_with "imagemagick", because: "both install `convert` binaries"
def conflicts_with(*names) @@ -3132,11 +3168,6 @@ class Formula skip_clean_paths.merge(paths) end - # @private - def skip_clean_paths - @skip_clean_paths ||= Set.new - end - # Software that will not be symlinked into the `brew --prefix` and will # only live in its Cellar. Other formulae can depend on it and Homebrew # will add the necessary includes, libraries, and other paths while @@ -3240,7 +3271,6 @@ class Formula # regex /foo-(\d+(?:\.\d+)+)\.tar/ # end def livecheck(&block) - @livecheck ||= Livecheck.new(self) return @livecheck unless block @livecheckable = true @@ -3396,11 +3426,6 @@ class Formula link_overwrite_paths.merge(paths) end - # @private - def link_overwrite_paths - @link_overwrite_paths ||= Set.new - end - # Permit links to certain libraries that don't exist. Available on Linux only. def ignore_missing_libraries(*libs) unless Homebrew::SimulateSystem.simulating_or_running_on_linux? @@ -3414,11 +3439,6 @@ class Formula allowed_missing_libraries.merge(libraries) end - - # @private - def allowed_missing_libraries - @allowed_missing_libraries ||= Set.new - end end end diff --git a/Library/Homebrew/options.rb b/Library/Homebrew/options.rb index 70c537a0f5..1126ab2260 100644 --- a/Library/Homebrew/options.rb +++ b/Library/Homebrew/options.rb @@ -82,9 +82,20 @@ class Options end def initialize(*args) + # Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans) @options = Set.new(*args) end + def initialize_dup(other) + super + @options = @options.dup + end + + def freeze + @options.dup + super + end + def each(*args, &block) @options.each(*args, &block) end diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index b3a127bc59..a8c98590c2 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -29,6 +29,7 @@ class Resource attr_accessor :name def initialize(name = nil, &block) + # Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans) @name = name @url = nil @version = nil @@ -37,11 +38,35 @@ class Resource @checksum = nil @using = nil @patches = [] - @livecheck = nil + @livecheck = Livecheck.new(self) @livecheckable = false instance_eval(&block) if block end + def initialize_dup(other) + super + @name = @name.dup + @version = @version.dup + @mirrors = @mirrors.dup + @specs = @specs.dup + @checksum = @checksum.dup + @using = @using.dup + @patches = @patches.dup + @livecheck = @livecheck.dup + end + + def freeze + @name.freeze + @version.freeze + @mirrors.freeze + @specs.freeze + @checksum.freeze + @using.freeze + @patches.freeze + @livecheck.freeze + super + end + def owner=(owner) @owner = owner patches.each { |p| p.owner = owner } @@ -185,7 +210,6 @@ class Resource # regex /foo-(\d+(?:\.\d+)+)\.tar/ # end def livecheck(&block) - @livecheck ||= Livecheck.new(self) if block return @livecheck unless block @livecheckable = true diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index 1970f02cd4..e51f2fd275 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -36,6 +36,7 @@ class SoftwareSpec def_delegators :@resource, :sha256 def initialize(flags: []) + # Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans) @resource = Resource.new @resources = {} @dependency_collector = DependencyCollector.new @@ -50,9 +51,36 @@ class SoftwareSpec @uses_from_macos_elements = [] end - def initialize_copy(other) + def initialize_dup(other) super + @resource = @resource.dup + @resources = @resources.dup @dependency_collector = @dependency_collector.dup + @bottle_specification = @bottle_specification.dup + @patches = @patches.dup + @options = @options.dup + @flags = @flags.dup + @deprecated_flags = @deprecated_flags.dup + @deprecated_options = @deprecated_options.dup + @build = @build.dup + @compiler_failures = @compiler_failures.dup + @uses_from_macos_elements = @uses_from_macos_elements.dup + end + + def freeze + @resource.freeze + @resources.freeze + @dependency_collector.freeze + @bottle_specification.freeze + @patches.freeze + @options.freeze + @flags.freeze + @deprecated_flags.freeze + @deprecated_options.freeze + @build.freeze + @compiler_failures.freeze + @uses_from_macos_elements.freeze + super end def owner=(owner) diff --git a/Library/Homebrew/test/resource_spec.rb b/Library/Homebrew/test/resource_spec.rb index 96541c3183..da3206393a 100644 --- a/Library/Homebrew/test/resource_spec.rb +++ b/Library/Homebrew/test/resource_spec.rb @@ -66,10 +66,6 @@ describe Resource do end describe "#livecheck" do - it "returns nil if livecheck block is not set in resource" do - expect(resource.livecheck).to be_nil - end - specify "when livecheck block is set" do expect(livecheck_resource.livecheck.url).to eq("https://brew.sh/test/releases") expect(livecheck_resource.livecheck.regex).to eq(/foo[._-]v?(\d+(?:\.\d+)+)\.t/i)