8 description: List of casks to audit (comma-separated)
11 description: Skip installation of casks
16 description: Apply new cask audit
23 HOMEBREW_NO_AUTO_UPDATE: 1
24 HOMEBREW_NO_INSTALL_FROM_API: 1
25 HOMEBREW_GITHUB_API_TOKEN: ${{ github.token }}
28 group: "${{ github.ref }}"
29 cancel-in-progress: ${{ github.event_name == 'pull_request' }}
37 matrix: ${{ steps.generate-matrix.outputs.matrix }}
40 - name: Set up Homebrew
42 uses: Homebrew/actions/setup-homebrew@master
48 - name: Check out Pull Request
49 uses: actions/checkout@v4
52 persist-credentials: false
54 - name: Generate CI matrix
57 if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]
59 brew ruby -- "$(brew --repository homebrew/cask)/cmd/lib/generate-matrix.rb" ${{ github.event.inputs.skip_install && '--skip-install' }} ${{ github.event.inputs.new_cask && '--new' }} --casks=${{ github.event.inputs.casks }}
61 brew ruby -- "$(brew --repository homebrew/cask)/cmd/lib/generate-matrix.rb" --url="${{ github.event.pull_request.url }}"
65 name: ${{ matrix.name }}
66 needs: generate-matrix
67 runs-on: ${{ matrix.runner }}
71 include: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
73 - name: Set up Homebrew
75 uses: Homebrew/actions/setup-homebrew@master
81 - name: Enable debug mode
83 echo 'HOMEBREW_DEBUG=1' >> "${GITHUB_ENV}"
84 echo 'HOMEBREW_VERBOSE=1' >> "${GITHUB_ENV}"
87 - name: Check out Pull Request
88 uses: actions/checkout@v4
91 persist-credentials: false
93 - name: Clean up CI machine
95 if [ "${{ matrix.runner }}" == 'macos-12' ] && ! brew uninstall --cask julia && ! rm -r /Applications/Julia-*.app; then
96 echo '::warning::Removing Julia is no longer necessary.'
99 if ! rm /usr/local/bin/dotnet; then
100 echo '::warning::Removing `dotnet` symlink is no longer necessary.'
103 if ! rm /usr/local/bin/pod; then
104 echo '::warning::Removing `cocoapods` symlink is no longer necessary.'
107 if ! rm /usr/local/bin/chromedriver; then
108 echo '::warning::Removing `chromedriver` symlink is no longer necessary.'
111 brew unlink python && brew link --overwrite python
112 if: runner.os == 'macOS'
114 - name: Cache Homebrew Gems
116 uses: actions/cache@v4
118 path: ${{ steps.set-up-homebrew.outputs.gems-path }}
119 key: ${{ matrix.runner }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
120 restore-keys: ${{ matrix.runner }}-rubygems-
122 - name: Run brew test-bot --only-tap-syntax
124 run: brew test-bot --tap '${{ matrix.tap }}' --only-tap-syntax
125 if: always() && !matrix.cask
127 - name: Run brew fetch --cask ${{ matrix.cask.token }}
130 brew fetch --cask --retry --force ${{ join(matrix.fetch_args, ' ') }} '${{ matrix.cask.path }}'
134 contains(fromJSON('["success", "skipped"]'), steps.tap-syntax.outcome) &&
137 - name: Run brew audit --cask${{ (matrix.cask && ' ') || ' --tap ' }}${{ matrix.cask.token || matrix.tap }}
140 brew audit --cask ${{ join(matrix.audit_args, ' ') }}${{ (matrix.cask && ' ') || ' --tap ' }}'${{ matrix.cask.token || matrix.tap }}'
144 contains(fromJSON('["success", "skipped"]'), steps.tap-syntax.outcome) &&
145 (!matrix.cask || steps.fetch.outcome == 'success') &&
148 - name: Gather cask information
151 brew tap homebrew/cask-versions
153 require 'cask/cask_loader'
154 require 'cask/installer'
156 cask = Cask::CaskLoader.load('${{ matrix.cask.path }}')
158 was_installed = cask.installed?
159 manual_installer = cask.artifacts.any? { |artifact|
160 artifact.is_a?(Cask::Artifact::Installer::ManualInstaller)
163 macos_requirement_satisfied = if macos_requirement = cask.depends_on.macos
164 macos_requirement.satisfied?
169 cask_conflicts = cask.conflicts_with&.dig(:cask).to_a.select { |c| Cask::CaskLoader.load(c).installed? }
170 formula_conflicts = cask.conflicts_with&.dig(:formula).to_a.select { |f| Formula[f].any_version_installed? }
172 installer = Cask::Installer.new(cask)
173 cask_and_formula_dependencies = installer.missing_cask_and_formula_dependencies
175 cask_dependencies = cask_and_formula_dependencies.select { |d| d.is_a?(Cask::Cask) }.map(&:full_name)
176 formula_dependencies = cask_and_formula_dependencies.select { |d| d.is_a?(Formula) }.map(&:full_name)
178 File.open(ENV.fetch("GITHUB_OUTPUT"), "a") do |f|
179 f.puts "was_installed=#{JSON.generate(was_installed)}"
180 f.puts "manual_installer=#{JSON.generate(manual_installer)}"
181 f.puts "macos_requirement_satisfied=#{JSON.generate(macos_requirement_satisfied)}"
182 f.puts "cask_conflicts=#{JSON.generate(cask_conflicts)}"
183 f.puts "cask_dependencies=#{JSON.generate(cask_dependencies)}"
184 f.puts "formula_conflicts=#{JSON.generate(formula_conflicts)}"
185 f.puts "formula_dependencies=#{JSON.generate(formula_dependencies)}"
188 if: always() && steps.fetch.outcome == 'success' && matrix.cask
190 - name: Uninstall conflicting formulae
192 brew uninstall --formula ${{ join(fromJSON(steps.info.outputs.formula_conflicts), ' ') }}
193 if: always() && steps.info.outcome == 'success' && join(fromJSON(steps.info.outputs.formula_conflicts)) != ''
196 - name: Uninstall conflicting casks
198 brew uninstall --cask ${{ join(fromJSON(steps.info.outputs.cask_conflicts), ' ') }}
199 if: always() && steps.info.outcome == 'success' && join(fromJSON(steps.info.outputs.cask_conflicts)) != ''
202 - name: Run brew uninstall --cask --zap ${{ matrix.cask.token }}
204 brew uninstall --cask --zap '${{ matrix.cask.path }}'
205 if: always() && steps.info.outcome == 'success' && fromJSON(steps.info.outputs.was_installed)
208 - name: Take snapshot of installed and running apps and services
211 brew ruby -r "$(brew --repository homebrew/cask)/cmd/lib/check.rb" <<'EOF'
212 File.open(ENV.fetch("GITHUB_OUTPUT"), "a") do |f|
213 f.puts "before=#{JSON.generate(Check.all)}"
216 if: always() && steps.info.outcome == 'success'
218 - name: Run brew install --cask ${{ matrix.cask.token }}
220 run: brew install --cask '${{ matrix.cask.path }}'
222 always() && steps.info.outcome == 'success' &&
223 fromJSON(steps.info.outputs.macos_requirement_satisfied) &&
227 - name: Run brew uninstall --cask ${{ matrix.cask.token }}
228 run: brew uninstall --cask '${{ matrix.cask.path }}'
229 if: always() && steps.install.outcome == 'success' && !fromJSON(steps.info.outputs.manual_installer)
232 - name: Uninstall formula dependencies
234 brew uninstall --formula ${{ join(fromJSON(steps.info.outputs.formula_dependencies), ' ') }}
235 if: always() && steps.install.outcome == 'success' && join(fromJSON(steps.info.outputs.formula_dependencies)) != ''
238 - name: Uninstall cask dependencies
240 brew uninstall --cask ${{ join(fromJSON(steps.info.outputs.cask_dependencies), ' ') }}
241 if: always() && steps.install.outcome == 'success' && join(fromJSON(steps.info.outputs.cask_dependencies)) != ''
244 - name: Compare installed and running apps and services with snapshot
246 brew ruby -r "$(brew --repository homebrew/cask)/cmd/lib/check.rb" <<'EOF'
247 require "cask/cask_loader"
248 require "utils/github/actions"
250 before = JSON.parse(<<~'EOS').transform_keys(&:to_sym)
251 ${{ steps.snapshot.outputs.before }}
255 cask = Cask::CaskLoader.load('${{ matrix.cask.path }}')
256 errors = Check.errors(before, after, cask: cask)
258 errors.each do |error|
260 puts GitHub::Actions::Annotation.new(:error, error, file: '${{ matrix.cask.path }}')
263 exit 1 if errors.any?
265 if: always() && steps.snapshot.outcome == 'success'
270 runs-on: ubuntu-latest
274 run: ${{ needs.test.result == 'success' }}