Skip to content

Commit 507686e

Browse files
committed
formulae_dependents: apply some optimisations
1. Doing `brew install --only-dependencies` and `brew postinstall` can be quite expensive. Let's defer this to when we're sure we have dependents to test. 2. Calling `brew uses` is slow, because this requires traversing the dependency tree. [*] Let's avoid doing this unless we know we really need to. Here, we test for "needing to" by checking if another `.rb` file in the tap that might be a formula contains a `depends_on` line declaring a dependency on the formula being tested. 3. Restrict the second `brew uses` call to when we are building dependents from source, since that is the only instance where we are interested in the build dependents. While we're here, make sure to call `brew postinstall` on all dependencies that were rebuilt, and not just the one being tested currently. This may address Homebrew#805. Locally, this results in the following speed up for a formula with no dependents: Before ( brew test-bot --only-formulae-dependents --testing-formulae=hello --dry-run) 28.95s user 6.70s system 76% cpu 46.875 total After ( brew test-bot --only-formulae-dependents --testing-formulae=hello --dry-run) 0.91s user 1.02s system 51% cpu 3.738 total This makes testing formulae with dependents slightly slower. However, the vast majority of formulae in Homebrew/core have no dependents (on macOS, at least), so this is likely a net win for the average workflow. [*] Potential future optimisation: calling `Dependency.expand` directly might give us better opportunities to exploit caching.
1 parent d17dd36 commit 507686e

File tree

1 file changed

+44
-14
lines changed

1 file changed

+44
-14
lines changed

lib/tests/formulae_dependents.rb

+44-14
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ class FormulaeDependents < TestFormulae
88
def run!(args:)
99
@source_tested_dependents = []
1010
@bottle_tested_dependents = []
11+
@testable_formulae = @testing_formulae - skipped_or_failed_formulae
12+
@postinstalled_formulae = []
1113

12-
(@testing_formulae - skipped_or_failed_formulae).each do |f|
14+
@testable_formulae.each do |f|
1315
dependent_formulae!(f, args: args)
1416
puts
1517
end
@@ -22,19 +24,29 @@ def dependent_formulae!(formula_name, args:)
2224

2325
test_header(:FormulaeDependents, method: "dependent_formulae!(#{formula_name})")
2426

27+
formula = Formulary.factory(formula_name)
28+
29+
source_dependents, bottled_dependents, testable_dependents =
30+
dependents_for_formula(formula, formula_name, args: args)
31+
32+
return if source_dependents.blank? &&
33+
bottled_dependents.blank? &&
34+
testable_dependents.blank?
35+
2536
# Install formula dependencies. These will have been uninstalled after building.
2637
test "brew", "install", "--only-dependencies", formula_name,
2738
env: { "HOMEBREW_DEVELOPER" => nil }
2839
return if steps.last.failed?
2940

3041
# Restore etc/var files that may have been nuked in the build stage.
31-
test "brew", "postinstall", formula_name
42+
formula_dependencies = Utils.safe_popen_read("brew", "deps", formula_name).split("\n")
43+
# Some dependencies will need postinstalling too.
44+
postinstall_dependencies = @testable_formulae & formula_dependencies
45+
postinstall_formulae = [formula_name, *postinstall_dependencies] - @postinstalled_formulae
46+
test "brew", "postinstall", *postinstall_formulae
3247
return if steps.last.failed?
3348

34-
formula = Formulary.factory(formula_name)
35-
36-
source_dependents, bottled_dependents, testable_dependents =
37-
dependents_for_formula(formula, formula_name, args: args)
49+
@postinstalled_formulae += postinstall_formulae
3850

3951
source_dependents.each do |dependent|
4052
next if @source_tested_dependents.include?(dependent)
@@ -54,6 +66,21 @@ def dependent_formulae!(formula_name, args:)
5466
end
5567

5668
def dependents_for_formula(formula, formula_name, args:)
69+
# Calling `brew uses` is slow, so let's reserve doing that for
70+
# if we can find a formula in the same tap that declares a dependency
71+
# on the formula we are testing.
72+
has_dependents = formula.tap.potential_formula_dirs.any? do |dir|
73+
next false unless dir.exist?
74+
75+
dir.children.any? do |child|
76+
next false unless child.file?
77+
next false unless child.extname == ".rb"
78+
79+
child.read.include? "depends_on \"#{formula_name}\""
80+
end
81+
end
82+
return unless has_dependents
83+
5784
info_header "Determining dependents..."
5885

5986
uses_args = %w[--formula --eval-all]
@@ -64,13 +91,16 @@ def dependents_for_formula(formula, formula_name, args:)
6491
.split("\n")
6592
end
6693

67-
# TODO: Consider handling the following case better.
68-
# `foo` has a build dependency on `bar`, and `bar` has a runtime dependency on
69-
# `baz`. When testing `baz` with `--build-dependents-from-source`, `foo` is
70-
# not tested, but maybe should be.
71-
dependents += with_env(HOMEBREW_STDERR: "1") do
72-
Utils.safe_popen_read("brew", "uses", *uses_args, "--include-build", formula_name)
73-
.split("\n")
94+
# We care about build dependents only if we are building them from source.
95+
if args.build_dependents_from_source?
96+
# TODO: Consider handling the following case better.
97+
# `foo` has a build dependency on `bar`, and `bar` has a runtime dependency on
98+
# `baz`. When testing `baz` with `--build-dependents-from-source`, `foo` is
99+
# not tested, but maybe should be.
100+
dependents += with_env(HOMEBREW_STDERR: "1") do
101+
Utils.safe_popen_read("brew", "uses", *uses_args, "--include-build", formula_name)
102+
.split("\n")
103+
end
74104
end
75105
dependents&.uniq!
76106
dependents&.sort!
@@ -104,7 +134,7 @@ def dependents_for_formula(formula, formula_name, args:)
104134
next false if OS.linux? && dependent.requirements.exclude?(LinuxRequirement.new)
105135

106136
all_deps_bottled_or_built = deps.all? do |d|
107-
bottled_or_built?(d.to_formula, @testing_formulae - @skipped_or_failed_formulae)
137+
bottled_or_built?(d.to_formula, @testable_formulae)
108138
end
109139
args.build_dependents_from_source? && all_deps_bottled_or_built
110140
end

0 commit comments

Comments
 (0)