Skip to content

Commit c7ba27b

Browse files
committed
Update DSL to make it more flexible
1 parent 300fcf1 commit c7ba27b

File tree

5 files changed

+73
-33
lines changed

5 files changed

+73
-33
lines changed

Library/Homebrew/ast_constants.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
[{ name: :needs, type: :method_call }],
4747
[{ name: :allow_network_access!, type: :method_call }],
4848
[{ name: :deny_network_access!, type: :method_call }],
49+
[{ name: :allow_in_sandbox!, type: :method_call }],
4950
[{ name: :install, type: :method_definition }],
5051
[{ name: :post_install, type: :method_definition }],
5152
[{ name: :caveats, type: :method_definition }],

Library/Homebrew/dev-cmd/test.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,16 @@ def run
8686
sandbox = Sandbox.new
8787
f.logs.mkpath
8888
sandbox.record_log(f.logs/"test.sandbox.log")
89-
sandbox.allow_write_temp_and_cache(f)
89+
sandbox.allow_write_temp_and_cache
9090
sandbox.allow_write_log(f)
9191
sandbox.allow_write_xcode
9292
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/cache")
9393
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/homebrew/locks")
9494
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/log")
9595
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/run")
9696
sandbox.deny_all_network_except_pipe(error_pipe) unless f.class.network_access_allowed?(:test)
97+
sandbox.allow_write_global_temp if f.allowed_in_sandbox?(:write_to_temp, phase: :test)
98+
sandbox.deny_signal if f.allowed_in_sandbox?(:signal, phase: :test)
9799
sandbox.exec(*exec_args)
98100
else
99101
exec(*exec_args)

Library/Homebrew/formula.rb

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,13 +1551,17 @@ def link_overwrite?(path)
15511551
# @see .disable!
15521552
delegate disable_reason: :"self.class"
15531553

1554-
# Sandbox rules that should be skipped when installing or testing this {Formula}.
1555-
# Returns `nil` if there are no sandbox rules to skip.
1556-
# @!method reduced_sandbox
1557-
# @return [Array<Symbol>]
1558-
# @see .reduce_sandbox
1559-
def reduced_sandbox
1560-
self.class.reduced_sandbox || []
1554+
# Whether or not the given sandbox rule should be skipped during the given phase in this {Formula}.
1555+
# @!method allowed_in_sandbox?
1556+
# @param type [Symbol] the type of sandbox rule
1557+
# @param phase [Symbol] the phase to check
1558+
# @return [Boolean]
1559+
# @see .allow_in_sandbox
1560+
def allowed_in_sandbox?(type, phase:)
1561+
return false unless self.class.allowed_in_sandbox
1562+
return false unless self.class.allowed_in_sandbox.key?(phase)
1563+
1564+
self.class.allowed_in_sandbox[phase].include?(type)
15611565
end
15621566

15631567
sig { returns(T::Boolean) }
@@ -3287,7 +3291,7 @@ def freeze
32873291
attr_reader :keg_only_reason
32883292

32893293
# The types of sandbox restrictions that should be lifted from the formula.
3290-
attr_reader :reduced_sandbox
3294+
attr_reader :allowed_in_sandbox
32913295

32923296
# A one-line description of the software. Used by users to get an overview
32933297
# of the software and Homebrew maintainers.
@@ -4309,29 +4313,59 @@ def link_overwrite(*paths)
43094313
link_overwrite_paths.merge(paths)
43104314
end
43114315

4312-
# Skip certain sandbox restrictions when installing this formula.
4316+
# Skip certain sandbox restrictions when installing and testing this formula.
43134317
# This can be useful if the upstream build system needs to write to
4314-
# locations that are protected by sandbox restrictions.
4318+
# locations that are protected by sandbox restrictions. Passing a
4319+
# phase is optional, and if not provided, the rule will be applied to
4320+
# all phases. The possible phases are `:build`, `:postinstall`, and `:test`.
43154321
#
43164322
# ### Example
43174323
#
4318-
# If upstream needs to write to `/private/tmp`:
4324+
# If the formula needs to write to `/private/tmp` in all phases:
43194325
#
43204326
# ```ruby
4321-
# reduce_sandbox :allow_write_to_temp
4327+
# allow_in_sandbox! :write_to_temp
43224328
# ```
4323-
def reduce_sandbox!(*types)
4324-
invalid_types = types.select { |type| Sandbox::SANDBOX_REDUCTIONS.exclude?(type) }
4329+
#
4330+
# If the formula needs to send signals in the `:test` phase:
4331+
# ```ruby
4332+
# allow_in_sandbox! :signal, phase: :test
4333+
# ```
4334+
#
4335+
# If the formula needs to write to `/private/tmp` and send signals
4336+
# in the `:test` and `:install` phase:
4337+
# ```ruby
4338+
# allow_in_sandbox! :write_to_temp, :signal, phase: [:test, :install]
4339+
# ```
4340+
def allow_in_sandbox!(*types, phase: nil)
4341+
invalid_types = types.select { |type| Sandbox::SANDBOX_DSL_RULES.exclude?(type) }
43254342
if invalid_types.any?
43264343
noun = if invalid_types.count > 1
43274344
"types"
43284345
else
43294346
"type"
43304347
end
4331-
raise ArgumentError, "Unsupported sandbox reduction #{noun}: #{invalid_types.join(", ")}"
4348+
raise ArgumentError, "Unsupported allow in sandbox item #{noun}: #{invalid_types.join(", ")}"
4349+
end
4350+
4351+
phase ||= Sandbox::SANDBOX_DSL_PHASES
4352+
phases = Array(phase)
4353+
invalid_phases = phases.select { |p| Sandbox::SANDBOX_DSL_PHASES.exclude?(p) }
4354+
if invalid_phases.any?
4355+
noun = if invalid_phases.count > 1
4356+
"phases"
4357+
else
4358+
"phase"
4359+
end
4360+
raise ArgumentError, "Unsupported sandbox phase #{noun}: #{invalid_phases.join(", ")}"
43324361
end
43334362

4334-
@reduced_sandbox = types
4363+
@allowed_in_sandbox ||= {}
4364+
phases.each do |p|
4365+
@allowed_in_sandbox[p] ||= []
4366+
@allowed_in_sandbox[p].concat(types)
4367+
@allowed_in_sandbox[p] = @allowed_in_sandbox[p].uniq
4368+
end
43354369
end
43364370
end
43374371
end

Library/Homebrew/formula_installer.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -937,14 +937,15 @@ def build
937937
formula.logs.mkpath
938938
sandbox.record_log(formula.logs/"build.sandbox.log")
939939
sandbox.allow_write_path(Dir.home) if interactive?
940-
sandbox.allow_write_temp_and_cache(formula)
940+
sandbox.allow_write_temp_and_cache
941941
sandbox.allow_write_log(formula)
942942
sandbox.allow_cvs
943943
sandbox.allow_fossil
944944
sandbox.allow_write_xcode
945945
sandbox.allow_write_cellar(formula)
946-
sandbox.deny_signal(formula)
947946
sandbox.deny_all_network_except_pipe(error_pipe) unless formula.network_access_allowed?(:build)
947+
sandbox.allow_write_global_temp if formula.allowed_in_sandbox?(:write_to_temp, phase: :build)
948+
sandbox.deny_signal if formula.allowed_in_sandbox?(:signal, phase: :build)
948949
sandbox.exec(*args)
949950
else
950951
exec(*args)
@@ -1154,7 +1155,7 @@ def post_install
11541155
sandbox = Sandbox.new
11551156
formula.logs.mkpath
11561157
sandbox.record_log(formula.logs/"postinstall.sandbox.log")
1157-
sandbox.allow_write_temp_and_cache(formula)
1158+
sandbox.allow_write_temp_and_cache
11581159
sandbox.allow_write_log(formula)
11591160
sandbox.allow_write_xcode
11601161
sandbox.deny_write_homebrew_repository
@@ -1163,6 +1164,8 @@ def post_install
11631164
Keg::KEG_LINK_DIRECTORIES.each do |dir|
11641165
sandbox.allow_write_path "#{HOMEBREW_PREFIX}/#{dir}"
11651166
end
1167+
sandbox.allow_write_global_temp if formula.allowed_in_sandbox?(:write_to_temp, phase: :postinstall)
1168+
sandbox.deny_signal if formula.allowed_in_sandbox?(:signal, phase: :postinstall)
11661169
sandbox.exec(*args)
11671170
else
11681171
exec(*args)

Library/Homebrew/sandbox.rb

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ class Sandbox
1111
SANDBOX_EXEC = "/usr/bin/sandbox-exec"
1212
private_constant :SANDBOX_EXEC
1313

14-
SANDBOX_REDUCTIONS = [:allow_write_to_temp, :allow_signal].freeze
14+
SANDBOX_DSL_RULES = [:write_to_temp, :signal].freeze
15+
SANDBOX_DSL_PHASES = [:build, :postinstall, :test].freeze
1516

1617
sig { returns(T::Boolean) }
1718
def self.available?
@@ -57,23 +58,22 @@ def deny_write_path(path)
5758
deny_write path:, type: :subpath
5859
end
5960

60-
sig { params(formula: T.nilable(Formula)).void }
61-
def allow_write_temp_and_cache(formula = nil)
62-
if formula&.reduced_sandbox&.include?(:allow_write_to_temp)
63-
allow_write_path "/private/tmp"
64-
allow_write_path "/private/var/tmp"
65-
end
61+
sig { void }
62+
def allow_write_temp_and_cache
6663
allow_write path: "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex
6764
allow_write_path HOMEBREW_TEMP
6865
allow_write_path HOMEBREW_CACHE
6966
end
7067

71-
sig { params(formula: T.nilable(Formula)).void }
72-
def deny_signal(formula = nil)
73-
puts "deny_signal: #{formula&.reduced_sandbox&.include?(:allow_signal)}"
74-
unless formula&.reduced_sandbox&.include?(:allow_signal)
75-
add_rule allow: false, operation: "signal", filter: "target others"
76-
end
68+
sig { void }
69+
def allow_write_global_temp
70+
allow_write_path "/private/tmp"
71+
allow_write_path "/private/var/tmp"
72+
end
73+
74+
sig { void }
75+
def deny_signal
76+
add_rule allow: false, operation: "signal", filter: "target others"
7777
end
7878

7979
sig { void }

0 commit comments

Comments
 (0)