Freeze formula definition once first instance is created

This commit is contained in:
Bo Anderson 2022-08-24 23:53:35 +01:00
parent 15280ba107
commit abfbb46678
No known key found for this signature in database
GPG Key ID: 3DB94E204E137D65
8 changed files with 138 additions and 38 deletions

View File

@ -33,9 +33,17 @@ class BuildEnvironment
module DSL module DSL
extend T::Sig 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) } sig { params(settings: Symbol).returns(BuildEnvironment) }
def env(*settings) def env(*settings)
@env ||= BuildEnvironment.new
@env.merge(settings) @env.merge(settings)
end end
end end

View File

@ -26,18 +26,25 @@ class DependencyCollector
sig { void } sig { void }
def initialize def initialize
# Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
@deps = Dependencies.new @deps = Dependencies.new
@requirements = Requirements.new @requirements = Requirements.new
init_global_dep_tree_if_needed! init_global_dep_tree_if_needed!
end end
def initialize_copy(other) def initialize_dup(other)
super super
@deps = @deps.dup @deps = @deps.dup
@requirements = @requirements.dup @requirements = @requirements.dup
end end
def freeze
@deps.freeze
@requirements.freeze
super
end
def add(spec) def add(spec)
case dep = fetch(spec) case dep = fetch(spec)
when Dependency when Dependency

View File

@ -5,11 +5,17 @@ class Module
def attr_rw(*attrs) def attr_rw(*attrs)
attrs.each do |attr| attrs.each do |attr|
module_eval <<-EOS, __FILE__, __LINE__+1 module_eval <<-EOS, __FILE__, __LINE__+1
def #{attr}(val=nil) # def prefix(val=nil) def #{attr}(val=nil) # def prefix(val=nil)
@#{attr} ||= nil # @prefix ||= nil if val.nil? # if val.nil?
return @#{attr} if val.nil? # return @prefix if val.nil? if instance_variable_defined?(:@#{attr}) # if instance_variable_defined?(:@prefix)
@#{attr} = val # @prefix = val return @#{attr} # return @prefix
end # end else # else
return nil # return nil
end # end
end # end
#
@#{attr} = val # @prefix = val
end # end
EOS EOS
end end
end end

View File

@ -184,6 +184,11 @@ class Formula
# Only allow instances of subclasses. The base class does not hold any spec information (URLs etc). # 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 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 @name = name
@path = path @path = path
@alias_path = alias_path @alias_path = alias_path
@ -2699,6 +2704,22 @@ class Formula
include BuildEnvironment::DSL include BuildEnvironment::DSL
include OnSystem::MacOSAndLinux 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) def method_added(method)
super super
@ -2710,6 +2731,16 @@ class Formula
end end
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 # 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`). # (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`).
# @private # @private
@ -2790,6 +2821,18 @@ class Formula
# @private # @private
attr_reader :plist_manual 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 # If `pour_bottle?` returns `false` the user-visible reason to display for
# why they cannot use the bottle. # why they cannot use the bottle.
# @private # @private
@ -2820,7 +2863,7 @@ class Formula
# A list of the {.stable} and {.head} {SoftwareSpec}s. # A list of the {.stable} and {.head} {SoftwareSpec}s.
# @private # @private
def specs def specs
@specs ||= [stable, head].freeze [stable, head].freeze
end end
# @!attribute [w] url # @!attribute [w] url
@ -2937,7 +2980,6 @@ class Formula
# depends_on "libffi" # depends_on "libffi"
# end</pre> # end</pre>
def stable(&block) def stable(&block)
@stable ||= SoftwareSpec.new(flags: build_flags)
return @stable unless block return @stable unless block
@stable.instance_eval(&block) @stable.instance_eval(&block)
@ -2956,7 +2998,6 @@ class Formula
# or (if autodetect fails): # or (if autodetect fails):
# <pre>head "https://hg.is.awesome.but.git.has.won.example.com/", using: :hg</pre> # <pre>head "https://hg.is.awesome.but.git.has.won.example.com/", using: :hg</pre>
def head(val = nil, specs = {}, &block) def head(val = nil, specs = {}, &block)
@head ||= HeadSoftwareSpec.new(flags: build_flags)
if block if block
@head.instance_eval(&block) @head.instance_eval(&block)
elsif val elsif val
@ -3107,11 +3148,6 @@ class Formula
@plist_manual = options[:manual] @plist_manual = options[:manual]
end end
# @private
def conflicts
@conflicts ||= []
end
# One or more formulae that conflict with this one and why. # One or more formulae that conflict with this one and why.
# <pre>conflicts_with "imagemagick", because: "both install `convert` binaries"</pre> # <pre>conflicts_with "imagemagick", because: "both install `convert` binaries"</pre>
def conflicts_with(*names) def conflicts_with(*names)
@ -3132,11 +3168,6 @@ class Formula
skip_clean_paths.merge(paths) skip_clean_paths.merge(paths)
end end
# @private
def skip_clean_paths
@skip_clean_paths ||= Set.new
end
# Software that will not be symlinked into the `brew --prefix` and will # 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 # only live in its Cellar. Other formulae can depend on it and Homebrew
# will add the necessary includes, libraries, and other paths while # will add the necessary includes, libraries, and other paths while
@ -3240,7 +3271,6 @@ class Formula
# regex /foo-(\d+(?:\.\d+)+)\.tar/ # regex /foo-(\d+(?:\.\d+)+)\.tar/
# end</pre> # end</pre>
def livecheck(&block) def livecheck(&block)
@livecheck ||= Livecheck.new(self)
return @livecheck unless block return @livecheck unless block
@livecheckable = true @livecheckable = true
@ -3396,11 +3426,6 @@ class Formula
link_overwrite_paths.merge(paths) link_overwrite_paths.merge(paths)
end 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. # Permit links to certain libraries that don't exist. Available on Linux only.
def ignore_missing_libraries(*libs) def ignore_missing_libraries(*libs)
unless Homebrew::SimulateSystem.simulating_or_running_on_linux? unless Homebrew::SimulateSystem.simulating_or_running_on_linux?
@ -3414,11 +3439,6 @@ class Formula
allowed_missing_libraries.merge(libraries) allowed_missing_libraries.merge(libraries)
end end
# @private
def allowed_missing_libraries
@allowed_missing_libraries ||= Set.new
end
end end
end end

View File

@ -82,9 +82,20 @@ class Options
end end
def initialize(*args) def initialize(*args)
# Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
@options = Set.new(*args) @options = Set.new(*args)
end end
def initialize_dup(other)
super
@options = @options.dup
end
def freeze
@options.dup
super
end
def each(*args, &block) def each(*args, &block)
@options.each(*args, &block) @options.each(*args, &block)
end end

View File

@ -29,6 +29,7 @@ class Resource
attr_accessor :name attr_accessor :name
def initialize(name = nil, &block) def initialize(name = nil, &block)
# Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
@name = name @name = name
@url = nil @url = nil
@version = nil @version = nil
@ -37,11 +38,35 @@ class Resource
@checksum = nil @checksum = nil
@using = nil @using = nil
@patches = [] @patches = []
@livecheck = nil @livecheck = Livecheck.new(self)
@livecheckable = false @livecheckable = false
instance_eval(&block) if block instance_eval(&block) if block
end 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) def owner=(owner)
@owner = owner @owner = owner
patches.each { |p| p.owner = owner } patches.each { |p| p.owner = owner }
@ -185,7 +210,6 @@ class Resource
# regex /foo-(\d+(?:\.\d+)+)\.tar/ # regex /foo-(\d+(?:\.\d+)+)\.tar/
# end</pre> # end</pre>
def livecheck(&block) def livecheck(&block)
@livecheck ||= Livecheck.new(self) if block
return @livecheck unless block return @livecheck unless block
@livecheckable = true @livecheckable = true

View File

@ -36,6 +36,7 @@ class SoftwareSpec
def_delegators :@resource, :sha256 def_delegators :@resource, :sha256
def initialize(flags: []) def initialize(flags: [])
# Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
@resource = Resource.new @resource = Resource.new
@resources = {} @resources = {}
@dependency_collector = DependencyCollector.new @dependency_collector = DependencyCollector.new
@ -50,9 +51,36 @@ class SoftwareSpec
@uses_from_macos_elements = [] @uses_from_macos_elements = []
end end
def initialize_copy(other) def initialize_dup(other)
super super
@resource = @resource.dup
@resources = @resources.dup
@dependency_collector = @dependency_collector.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 end
def owner=(owner) def owner=(owner)

View File

@ -66,10 +66,6 @@ describe Resource do
end end
describe "#livecheck" do 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 specify "when livecheck block is set" do
expect(livecheck_resource.livecheck.url).to eq("https://brew.sh/test/releases") 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) expect(livecheck_resource.livecheck.regex).to eq(/foo[._-]v?(\d+(?:\.\d+)+)\.t/i)