Merge branch 'upgrade-reinstall-size' into install-size

This commit is contained in:
thibhero 2025-02-16 00:07:31 -05:00
commit b457b764c9
143 changed files with 1327 additions and 60 deletions

View File

@ -53,3 +53,10 @@ updates:
interval: daily interval: daily
allow: allow:
- dependency-type: all - dependency-type: all
- package-ecosystem: pip
directory: /Library/Homebrew/formula-analytics/
schedule:
interval: daily
allow:
- dependency-type: all

View File

@ -78,7 +78,7 @@ jobs:
path: results.sarif path: results.sarif
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
with: with:
sarif_file: results.sarif sarif_file: results.sarif
category: zizmor category: zizmor

View File

@ -28,7 +28,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
with: with:
languages: ruby languages: ruby
config: | config: |
@ -36,4 +36,4 @@ jobs:
- Library/Homebrew/vendor - Library/Homebrew/vendor
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9

View File

@ -2,6 +2,9 @@ name: Docker
on: on:
pull_request: pull_request:
push:
branches:
- master
merge_group: merge_group:
release: release:
types: types:
@ -34,7 +37,7 @@ jobs:
run: git fetch origin master run: git fetch origin master
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
with: with:
cache-binary: false cache-binary: false
@ -74,7 +77,7 @@ jobs:
"homebrew/brew:latest" "homebrew/brew:latest"
) )
fi fi
elif [[ "${GITHUB_EVENT_NAME}" == "merge_group" && elif [[ "${GITHUB_EVENT_NAME}" == "push" &&
"${GITHUB_REF}" == "refs/heads/master" && "${GITHUB_REF}" == "refs/heads/master" &&
"${{ matrix.version }}" == "22.04" ]]; then "${{ matrix.version }}" == "22.04" ]]; then
tags+=( tags+=(

View File

@ -53,7 +53,7 @@ jobs:
run: vale docs/ run: vale docs/
- name: Install Ruby - name: Install Ruby
uses: ruby/setup-ruby@2654679fe7f7c29875c669398a8ec0791b8a64a1 # v1.215.0 uses: ruby/setup-ruby@d781c1b4ed31764801bfae177617bb0446f5ef8d # v1.218.0
with: with:
bundler-cache: true bundler-cache: true
working-directory: docs working-directory: docs

View File

@ -43,7 +43,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Install Ruby - name: Install Ruby
uses: ruby/setup-ruby@2654679fe7f7c29875c669398a8ec0791b8a64a1 # v1.215.0 uses: ruby/setup-ruby@d781c1b4ed31764801bfae177617bb0446f5ef8d # v1.218.0
with: with:
bundler-cache: true bundler-cache: true
working-directory: rubydoc working-directory: rubydoc

View File

@ -115,7 +115,6 @@ jobs:
run: | run: |
brew tap homebrew/bundle brew tap homebrew/bundle
brew tap homebrew/command-not-found brew tap homebrew/command-not-found
brew tap homebrew/formula-analytics
brew tap homebrew/portable-ruby brew tap homebrew/portable-ruby
brew tap homebrew/services brew tap homebrew/services
@ -129,7 +128,6 @@ jobs:
homebrew/test-bot homebrew/test-bot
brew style homebrew/command-not-found \ brew style homebrew/command-not-found \
homebrew/formula-analytics \
homebrew/portable-ruby homebrew/portable-ruby
- name: Run brew style on homebrew/cask - name: Run brew style on homebrew/cask
@ -182,9 +180,6 @@ jobs:
run: | run: |
brew audit --skip-style --except=version --tap=homebrew/cask brew audit --skip-style --except=version --tap=homebrew/cask
- name: Generate formula API
run: brew generate-formula-api --dry-run
- name: Generate cask API - name: Generate cask API
run: brew generate-cask-api --dry-run run: brew generate-cask-api --dry-run
@ -346,7 +341,7 @@ jobs:
filenames=$(find Library/Homebrew/test/junit -name 'rspec*.xml' -print | tr '\n' ',') filenames=$(find Library/Homebrew/test/junit -name 'rspec*.xml' -print | tr '\n' ',')
echo "filenames=${filenames%,}" >> "$GITHUB_OUTPUT" echo "filenames=${filenames%,}" >> "$GITHUB_OUTPUT"
- uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2 - uses: codecov/test-results-action@44ecb3a270cd942bdf0fa8f2ce14cb32493e810a # v1.0.3
with: with:
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
files: ${{ steps.junit_xml.outputs.filenames }} files: ${{ steps.junit_xml.outputs.filenames }}
@ -398,3 +393,46 @@ jobs:
- run: brew install gnu-tar - run: brew install gnu-tar
- run: brew test-bot --only-formulae --only-json-tab --test-default-formula - run: brew test-bot --only-formulae --only-json-tab --test-default-formula
test-analytics:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Setup Python
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version-file: ${{ steps.set-up-homebrew.outputs.repository-path }}/Library/Homebrew/formula-analytics/.python-version
- name: Cache Homebrew Bundler RubyGems
id: cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
restore-keys: ${{ runner.os }}-rubygems-
- name: Install Homebrew Bundler RubyGems
if: steps.cache.outputs.cache-hit != 'true'
run: brew install-bundler-gems
- run: brew formula-analytics --setup
- run: brew formula-analytics --install --json --days-ago=2
if: github.event.pull_request.head.repo.fork == false && (github.event_name == 'pull_request' && github.event.pull_request.user.login != 'dependabot[bot]')
env:
HOMEBREW_INFLUXDB_TOKEN: ${{ secrets.HOMEBREW_INFLUXDB_READ_TOKEN }}
- run: brew generate-analytics-api
if: github.event.pull_request.head.repo.fork == false && (github.event_name == 'pull_request' && github.event.pull_request.user.login != 'dependabot[bot]')
env:
HOMEBREW_INFLUXDB_TOKEN: ${{ secrets.HOMEBREW_INFLUXDB_READ_TOKEN }}

View File

@ -92,7 +92,7 @@ jobs:
fi fi
- name: Generate push token - name: Generate push token
uses: actions/create-github-app-token@136412a57a7081aa63c935a2cc2918f76c34f514 # v1.11.2 uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
id: app-token id: app-token
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
with: with:

1
.gitignore vendored
View File

@ -106,6 +106,7 @@
**/vendor/bundle/ruby/*/gems/prism-*/ **/vendor/bundle/ruby/*/gems/prism-*/
**/vendor/bundle/ruby/*/gems/psych-*/ **/vendor/bundle/ruby/*/gems/psych-*/
**/vendor/bundle/ruby/*/gems/pry-*/ **/vendor/bundle/ruby/*/gems/pry-*/
**/vendor/bundle/ruby/*/gems/pycall-*/
**/vendor/bundle/ruby/*/gems/racc-*/ **/vendor/bundle/ruby/*/gems/racc-*/
**/vendor/bundle/ruby/*/gems/rainbow-*/ **/vendor/bundle/ruby/*/gems/rainbow-*/
**/vendor/bundle/ruby/*/gems/rbi-*/ **/vendor/bundle/ruby/*/gems/rbi-*/

View File

@ -71,6 +71,9 @@ end
group :vscode, optional: true do group :vscode, optional: true do
gem "ruby-lsp", require: false gem "ruby-lsp", require: false
end end
group :formula_analytics, optional: true do
gem "pycall", require: false
end
# shared gems (used by multiple groups) # shared gems (used by multiple groups)
group :audit, :bump_unversioned_casks, :livecheck, optional: true do group :audit, :bump_unversioned_casks, :livecheck, optional: true do

View File

@ -43,6 +43,7 @@ GEM
coderay (~> 1.1) coderay (~> 1.1)
method_source (~> 1.0) method_source (~> 1.0)
public_suffix (6.0.1) public_suffix (6.0.1)
pycall (1.5.2)
racc (1.8.1) racc (1.8.1)
rainbow (3.1.1) rainbow (3.1.1)
rbi (0.2.4) rbi (0.2.4)
@ -113,15 +114,15 @@ GEM
simplecov-html (0.13.1) simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
simpleidn (0.2.3) simpleidn (0.2.3)
sorbet (0.5.11805) sorbet (0.5.11812)
sorbet-static (= 0.5.11805) sorbet-static (= 0.5.11812)
sorbet-runtime (0.5.11805) sorbet-runtime (0.5.11812)
sorbet-static (0.5.11805-aarch64-linux) sorbet-static (0.5.11812-aarch64-linux)
sorbet-static (0.5.11805-universal-darwin) sorbet-static (0.5.11812-universal-darwin)
sorbet-static (0.5.11805-x86_64-linux) sorbet-static (0.5.11812-x86_64-linux)
sorbet-static-and-runtime (0.5.11805) sorbet-static-and-runtime (0.5.11812)
sorbet (= 0.5.11805) sorbet (= 0.5.11812)
sorbet-runtime (= 0.5.11805) sorbet-runtime (= 0.5.11812)
spoom (1.5.3) spoom (1.5.3)
erubi (>= 1.10.0) erubi (>= 1.10.0)
prism (>= 0.28.0) prism (>= 0.28.0)
@ -152,6 +153,7 @@ GEM
PLATFORMS PLATFORMS
aarch64-linux aarch64-linux
arm-linux
arm64-darwin arm64-darwin
x86_64-darwin x86_64-darwin
x86_64-linux x86_64-linux
@ -168,6 +170,7 @@ DEPENDENCIES
patchelf patchelf
plist plist
pry pry
pycall
redcarpet redcarpet
rexml rexml
rspec rspec

View File

@ -63,6 +63,11 @@ module Homebrew
[:switch, "-g", "--git", { [:switch, "-g", "--git", {
description: "Create a Git repository, useful for creating patches to the software.", description: "Create a Git repository, useful for creating patches to the software.",
}], }],
[:switch, "--ask", {
description: "Ask for confirmation before downloading and upgrading formulae. " \
"Print bottles and dependencies download size, install and net install size.",
env: :ask,
}],
].each do |args| ].each do |args|
options = args.pop options = args.pop
send(*args, **options) send(*args, **options)
@ -126,6 +131,98 @@ module Homebrew
unless formulae.empty? unless formulae.empty?
Install.perform_preinstall_checks_once Install.perform_preinstall_checks_once
ask_input = lambda {
ohai "Do you want to proceed with the installation? [Y/y/yes/N/n]"
accepted_inputs = %w[y yes]
declined_inputs = %w[n no]
loop do
result = $stdin.gets.chomp.strip.downcase
if accepted_inputs.include?(result)
break
elsif declined_inputs.include?(result)
exit 0
else
puts "Invalid input. Please enter 'Y', 'y', or 'yes' to proceed, or 'N' to abort."
end
end
}
# Build a unique list of formulae to size by including:
# 1. The original formulae to install.
# 2. Their outdated dependents (subject to pruning criteria).
# 3. Optionally, any installed formula that depends on one of these and is outdated.
compute_sized_formulae = lambda { |f, check_dep: true, upgrade: true|
sized_formulae = f.flat_map do |formula|
# Always include the formula itself.
formula_list = [formula]
next unless upgrade
deps = args.build_from_source? ? formula.deps.build : formula.deps.required
# If there are dependencies, try to gather outdated, bottled ones.
if deps.any? && check_dep
outdated_dependents = deps.map(&:to_formula).reject(&:pinned?).select do |dep|
dep.installed_kegs.empty? || (dep.bottled? && dep.outdated?)
end
formula_list.concat(outdated_dependents)
end
formula_list
end
# Add any installed formula that depends on one of the sized formulae and is outdated.
if check_dep && !Homebrew::EnvConfig.no_installed_dependents_check?
sized_formulae.concat(Formula.installed.select do |installed_formula|
installed_formula.outdated? &&
installed_formula.deps.required.any? { |dep| sized_formulae.include?(dep.to_formula) }
end)
end
sized_formulae.uniq(&:to_s)
}
# Compute the total sizes (download, installed, and net) for the given formulae.
compute_total_sizes = lambda { |sized_formulae, debug: false|
total_download_size = 0
total_installed_size = 0
total_net_size = 0
sized_formulae.each do |formula|
next unless (bottle = formula.bottle)
# Fetch additional bottle metadata (if necessary).
bottle.fetch_tab(quiet: !debug)
total_download_size += bottle.bottle_size.to_i if bottle.bottle_size
total_installed_size += bottle.installed_size.to_i if bottle.installed_size
# Sum disk usage for all installed kegs of the formula.
next if formula.installed_kegs.none?
kegs_dep_size = formula.installed_kegs.sum { |keg| keg.disk_usage.to_i }
total_net_size += bottle.installed_size.to_i - kegs_dep_size if bottle.installed_size
end
{ download: total_download_size,
installed: total_installed_size,
net: total_net_size }
}
# Main block: if asking the user is enabled, show dependency and size information.
if args.ask?
ohai "Looking for bottles..."
sized_formulae = compute_sized_formulae.call(formulae, check_dep: false, upgrade: false)
sizes = compute_total_sizes.call(sized_formulae, debug: args.debug?)
puts "Formulae: #{sized_formulae.join(", ")}\n\n"
puts "Download Size: #{disk_usage_readable(sizes[:download])}"
puts "Install Size: #{disk_usage_readable(sizes[:installed])}"
puts "Net Install Size: #{disk_usage_readable(sizes[:net])}" if sizes[:net] != 0
ask_input.call
end
formulae.each do |formula| formulae.each do |formula|
if formula.pinned? if formula.pinned?
onoe "#{formula.full_name} is pinned. You must unpin it to reinstall." onoe "#{formula.full_name} is pinned. You must unpin it to reinstall."

View File

@ -71,6 +71,11 @@ module Homebrew
[:switch, "--overwrite", { [:switch, "--overwrite", {
description: "Delete files that already exist in the prefix while linking.", description: "Delete files that already exist in the prefix while linking.",
}], }],
[:switch, "--ask", {
description: "Ask for confirmation before downloading and upgrading formulae. " \
"Print bottles and dependencies download size, install and net install size.",
env: :ask,
}],
].each do |args| ].each do |args|
options = args.pop options = args.pop
send(*args, **options) send(*args, **options)
@ -216,6 +221,98 @@ module Homebrew
Install.perform_preinstall_checks_once Install.perform_preinstall_checks_once
ask_input = lambda {
ohai "Do you want to proceed with the installation? [Y/y/yes/N/n]"
accepted_inputs = %w[y yes]
declined_inputs = %w[n no]
loop do
result = $stdin.gets.chomp.strip.downcase
if accepted_inputs.include?(result)
break
elsif declined_inputs.include?(result)
exit 0
else
puts "Invalid input. Please enter 'Y', 'y', or 'yes' to proceed, or 'N' to abort."
end
end
}
# Build a unique list of formulae to size by including:
# 1. The original formulae to install.
# 2. Their outdated dependents (subject to pruning criteria).
# 3. Optionally, any installed formula that depends on one of these and is outdated.
compute_sized_formulae = lambda { |f, check_dep: true, upgrade: true|
sized_formulae = f.flat_map do |formula|
# Always include the formula itself.
formula_list = [formula]
next unless upgrade
deps = args.build_from_source? ? formula.deps.build : formula.deps.required
# If there are dependencies, try to gather outdated, bottled ones.
if deps.any? && check_dep
outdated_dependents = deps.map(&:to_formula).reject(&:pinned?).select do |dep|
dep.installed_kegs.empty? || (dep.bottled? && dep.outdated?)
end
formula_list.concat(outdated_dependents)
end
formula_list
end
# Add any installed formula that depends on one of the sized formulae and is outdated.
if check_dep && !Homebrew::EnvConfig.no_installed_dependents_check?
sized_formulae.concat(Formula.installed.select do |installed_formula|
installed_formula.outdated? &&
installed_formula.deps.required.any? { |dep| sized_formulae.include?(dep.to_formula) }
end)
end
sized_formulae.uniq(&:to_s)
}
# Compute the total sizes (download, installed, and net) for the given formulae.
compute_total_sizes = lambda { |sized_formulae, debug: false|
total_download_size = 0
total_installed_size = 0
total_net_size = 0
sized_formulae.each do |formula|
next unless (bottle = formula.bottle)
# Fetch additional bottle metadata (if necessary).
bottle.fetch_tab(quiet: !debug)
total_download_size += bottle.bottle_size.to_i if bottle.bottle_size
total_installed_size += bottle.installed_size.to_i if bottle.installed_size
# Sum disk usage for all installed kegs of the formula.
next if formula.installed_kegs.none?
kegs_dep_size = formula.installed_kegs.sum { |keg| keg.disk_usage.to_i }
total_net_size += bottle.installed_size.to_i - kegs_dep_size if bottle.installed_size
end
{ download: total_download_size,
installed: total_installed_size,
net: total_net_size }
}
# Main block: if asking the user is enabled, show dependency and size information.
if args.ask?
ohai "Looking for bottles..."
sized_formulae = compute_sized_formulae.call(formulae_to_install)
sizes = compute_total_sizes.call(sized_formulae, debug: args.debug?)
puts "Formulae: #{sized_formulae.join(", ")}\n\n"
puts "Download Size: #{disk_usage_readable(sizes[:download])}"
puts "Install Size: #{disk_usage_readable(sizes[:installed])}"
puts "Net Install Size: #{disk_usage_readable(sizes[:net])}" if sizes[:net] != 0
ask_input.call
end
Upgrade.upgrade_formulae( Upgrade.upgrade_formulae(
formulae_to_install, formulae_to_install,
flags: args.flags_only, flags: args.flags_only,

View File

@ -0,0 +1,392 @@
# typed: strict
# frozen_string_literal: true
require "abstract_command"
module Homebrew
module DevCmd
class FormulaAnalytics < AbstractCommand
cmd_args do
usage_banner <<~EOS
`formula-analytics`
Query Homebrew's analytics.
EOS
flag "--days-ago=",
description: "Query from the specified days ago until the present. The default is 30 days."
switch "--install",
description: "Output the number of specifically requested installations or installation as " \
"dependencies of the formula. This is the default."
switch "--cask-install",
description: "Output the number of installations of casks."
switch "--install-on-request",
description: "Output the number of specifically requested installations of the formula."
switch "--build-error",
description: "Output the number of build errors for the formulae."
switch "--os-version",
description: "Output OS versions."
switch "--homebrew-devcmdrun-developer",
description: "Output devcmdrun/HOMEBREW_DEVELOPER."
switch "--homebrew-os-arch-ci",
description: "Output OS/Architecture/CI."
switch "--homebrew-prefixes",
description: "Output Homebrew prefixes."
switch "--homebrew-versions",
description: "Output Homebrew versions."
switch "--brew-command-run",
description: "Output `brew` commands run."
switch "--brew-command-run-options",
description: "Output `brew` commands run with options."
switch "--brew-test-bot-test",
description: "Output `brew test-bot` steps run."
switch "--json",
description: "Output JSON. This is required: plain text support has been removed."
switch "--all-core-formulae-json",
description: "Output a different JSON format containing the JSON data for all " \
"Homebrew/homebrew-core formulae."
switch "--setup",
description: "Install the necessary gems, require them and exit without running a query."
conflicts "--install", "--cask-install", "--install-on-request", "--build-error", "--os-version",
"--homebrew-devcmdrun-developer", "--homebrew-os-arch-ci", "--homebrew-prefixes",
"--homebrew-versions", "--brew-command-run", "--brew-command-run-options", "--brew-test-bot-test"
conflicts "--json", "--all-core-formulae-json", "--setup"
named_args :none
end
FIRST_INFLUXDB_ANALYTICS_DATE = T.let(Date.new(2023, 03, 27).freeze, Date)
sig { override.void }
def run
Homebrew.install_bundler_gems!(groups: ["formula_analytics"])
setup_python
influx_analytics(args)
end
sig { void }
def setup_python
formula_analytics_root = HOMEBREW_LIBRARY/"Homebrew/formula-analytics"
vendor_python = Pathname.new("~/.brew-formula-analytics/vendor/python").expand_path
python_version = (formula_analytics_root/".python-version").read.chomp
which_python = which("python#{python_version}", ORIGINAL_PATHS)
odie <<~EOS if which_python.nil?
Python #{python_version} is required. Try:
brew install python@#{python_version}
EOS
venv_root = vendor_python/python_version
vendor_python.children.reject { |path| path == venv_root }.each(&:rmtree) if vendor_python.exist?
venv_python = venv_root/"bin/python"
repo_requirements = HOMEBREW_LIBRARY/"Homebrew/formula-analytics/requirements.txt"
venv_requirements = venv_root/"requirements.txt"
if !venv_requirements.exist? || !FileUtils.identical?(repo_requirements, venv_requirements)
safe_system which_python, "-I", "-m", "venv", "--clear", venv_root, out: :err
safe_system venv_python, "-m", "pip", "install",
"--disable-pip-version-check",
"--require-hashes",
"--requirement", repo_requirements,
out: :err
FileUtils.cp repo_requirements, venv_requirements
end
ENV["PATH"] = "#{venv_root}/bin:#{ENV.fetch("PATH")}"
ENV["__PYVENV_LAUNCHER__"] = venv_python.to_s # support macOS framework Pythons
require "pycall"
PyCall.init(venv_python)
require formula_analytics_root/"pycall-setup"
end
sig { params(args: Homebrew::DevCmd::FormulaAnalytics::Args).void }
def influx_analytics(args)
require "utils/analytics"
require "json"
return if args.setup?
odie "HOMEBREW_NO_ANALYTICS is set!" if ENV["HOMEBREW_NO_ANALYTICS"]
token = ENV.fetch("HOMEBREW_INFLUXDB_TOKEN", nil)
odie "No InfluxDB credentials found in HOMEBREW_INFLUXDB_TOKEN!" unless token
client = InfluxDBClient3.new(
token:,
host: URI.parse(Utils::Analytics::INFLUX_HOST).host,
org: Utils::Analytics::INFLUX_ORG,
database: Utils::Analytics::INFLUX_BUCKET,
)
max_days_ago = (Date.today - FIRST_INFLUXDB_ANALYTICS_DATE).to_s.to_i
days_ago = (args.days_ago || 30).to_i
if days_ago > max_days_ago
opoo "Analytics started #{FIRST_INFLUXDB_ANALYTICS_DATE}. `--days-ago` set to maximum value."
days_ago = max_days_ago
end
if days_ago > 365
opoo "Analytics are only retained for 1 year, setting `--days-ago=365`."
days_ago = 365
end
all_core_formulae_json = args.all_core_formulae_json?
categories = []
categories << :build_error if args.build_error?
categories << :cask_install if args.cask_install?
categories << :formula_install if args.install?
categories << :formula_install_on_request if args.install_on_request?
categories << :homebrew_devcmdrun_developer if args.homebrew_devcmdrun_developer?
categories << :homebrew_os_arch_ci if args.homebrew_os_arch_ci?
categories << :homebrew_prefixes if args.homebrew_prefixes?
categories << :homebrew_versions if args.homebrew_versions?
categories << :os_versions if args.os_version?
categories << :command_run if args.brew_command_run?
categories << :command_run_options if args.brew_command_run_options?
categories << :test_bot_test if args.brew_test_bot_test?
category_matching_buckets = [:build_error, :cask_install, :command_run, :test_bot_test]
categories.each do |category|
additional_where = all_core_formulae_json ? " AND tap_name ~ '^homebrew/(core|cask)$'" : ""
bucket = if category_matching_buckets.include?(category)
category
elsif category == :command_run_options
:command_run
else
:formula_install
end
case category
when :homebrew_devcmdrun_developer
dimension_key = "devcmdrun_developer"
groups = [:devcmdrun, :developer]
when :homebrew_os_arch_ci
dimension_key = "os_arch_ci"
groups = [:os, :arch, :ci]
when :homebrew_prefixes
dimension_key = "prefix"
groups = [:prefix, :os, :arch]
when :homebrew_versions
dimension_key = "version"
groups = [:version]
when :os_versions
dimension_key = :os_version
groups = [:os_name_and_version]
when :command_run
dimension_key = "command_run"
groups = [:command]
when :command_run_options
dimension_key = "command_run_options"
groups = [:command, :options, :devcmdrun, :developer]
additional_where += " AND ci = 'false'"
when :test_bot_test
dimension_key = "test_bot_test"
groups = [:command, :passed, :arch, :os]
when :cask_install
dimension_key = :cask
groups = [:package, :tap_name]
else
dimension_key = :formula
additional_where += " AND on_request = 'true'" if category == :formula_install_on_request
groups = [:package, :tap_name, :options]
end
sql_groups = groups.map { |e| "\"#{e}\"" }.join(",")
query = <<~EOS
SELECT #{sql_groups}, COUNT(*) AS "count" FROM "#{bucket}" WHERE time >= now() - INTERVAL '#{days_ago} day'#{additional_where} GROUP BY #{sql_groups}
EOS
batches = begin
client.query(query:, language: "sql").to_batches
rescue PyCall::PyError => e
if e.message.include?("message: unauthenticated")
odie "Could not authenticate with InfluxDB! Please check your HOMEBREW_INFLUXDB_TOKEN!"
end
raise
end
json = T.let({
category:,
total_items: 0,
start_date: Date.today - days_ago.to_i,
end_date: Date.today,
total_count: 0,
items: [],
}, T::Hash[Symbol, T.untyped])
batches.each do |batch|
batch.to_pylist.each do |record|
dimension = case category
when :homebrew_devcmdrun_developer
"devcmdrun=#{record["devcmdrun"]} HOMEBREW_DEVELOPER=#{record["developer"]}"
when :homebrew_os_arch_ci
if record["ci"] == "true"
"#{record["os"]} #{record["arch"]} (CI)"
else
"#{record["os"]} #{record["arch"]}"
end
when :homebrew_prefixes
if record["prefix"] == "custom-prefix"
"#{record["prefix"]} (#{record["os"]} #{record["arch"]})"
else
(record["prefix"]).to_s
end
when :os_versions
format_os_version_dimension(record["os_name_and_version"])
when :command_run_options
options = record["options"].split
# Cleanup bad data before TODO
# Can delete this code after 18th July 2025.
options.reject! { |option| option.match?(/^--with(out)?-/) }
next if options.any? { |option| option.match?(/^TMPDIR=/) }
"#{record["command"]} #{options.sort.join(" ")}"
when :test_bot_test
command_and_package, options = record["command"].split.partition { |arg| !arg.start_with?("-") }
# Cleanup bad data before https://github.com/Homebrew/homebrew-test-bot/pull/1043
# Can delete this code after 27th April 2025.
next if %w[audit install linkage style test].exclude?(command_and_package.first)
next if command_and_package.last.include?("/")
next if options.include?("--tap=")
next if options.include?("--only-dependencies")
next if options.include?("--cached")
command_and_options = (command_and_package + options.sort).join(" ")
passed = (record["passed"] == "true") ? "PASSED" : "FAILED"
"#{command_and_options} (#{record["os"]} #{record["arch"]}) (#{passed})"
else
record[groups.first.to_s]
end
next if dimension.blank?
if (tap_name = record["tap_name"].presence) &&
((tap_name != "homebrew/cask" && dimension_key == :cask) ||
(tap_name != "homebrew/core" && dimension_key == :formula))
dimension = "#{tap_name}/#{dimension}"
end
if (all_core_formulae_json || category == :build_error) &&
(options = record["options"].presence)
# homebrew/core formulae don't have non-HEAD options but they ended up in our analytics anyway.
if all_core_formulae_json
options = options.split.include?("--HEAD") ? "--HEAD" : ""
end
dimension = "#{dimension} #{options}"
end
dimension = dimension.strip
next if dimension.match?(/[<>]/)
count = record["count"]
json[:total_items] += 1
json[:total_count] += count
json[:items] << {
number: nil,
dimension_key => dimension,
count:,
}
end
end
odie "No data returned" if json[:total_count].zero?
# Combine identical values
deduped_items = {}
json[:items].each do |item|
key = item[dimension_key]
if deduped_items.key?(key)
deduped_items[key][:count] += item[:count]
else
deduped_items[key] = item
end
end
json[:items] = deduped_items.values
if all_core_formulae_json
core_formula_items = {}
json[:items].each do |item|
item.delete(:number)
formula_name, = item[dimension_key].split.first
next if formula_name.include?("/")
core_formula_items[formula_name] ||= []
core_formula_items[formula_name] << item
end
json.delete(:items)
core_formula_items.each_value do |items|
items.sort_by! { |item| -item[:count] }
items.each do |item|
item[:count] = format_count(item[:count])
end
end
json[:formulae] = core_formula_items.sort_by { |name, _| name }.to_h
else
json[:items].sort_by! do |item|
-item[:count]
end
json[:items].each_with_index do |item, index|
item[:number] = index + 1
percent = (item[:count].to_f / json[:total_count]) * 100
item[:percent] = format_percent(percent)
item[:count] = format_count(item[:count])
end
end
puts JSON.pretty_generate json
end
end
sig { params(count: Integer).returns(String) }
def format_count(count)
count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end
sig { params(percent: Float).returns(String) }
def format_percent(percent)
format("%<percent>.2f", percent:).gsub(/\.00$/, "")
end
sig { params(dimension: T.nilable(String)).returns(T.nilable(String)) }
def format_os_version_dimension(dimension)
return if dimension.blank?
dimension = dimension.gsub(/^Intel ?/, "")
.gsub(/^macOS ?/, "")
.gsub(/ \(.+\)$/, "")
case dimension
when "10.11", /^10\.11\.?/ then "OS X El Capitan (10.11)"
when "10.12", /^10\.12\.?/ then "macOS Sierra (10.12)"
when "10.13", /^10\.13\.?/ then "macOS High Sierra (10.13)"
when "10.14", /^10\.14\.?/ then "macOS Mojave (10.14)"
when "10.15", /^10\.15\.?/ then "macOS Catalina (10.15)"
when "10.16", /^11\.?/ then "macOS Big Sur (11)"
when /^12\.?/ then "macOS Monterey (12)"
when /^13\.?/ then "macOS Ventura (13)"
when /^14\.?/ then "macOS Sonoma (14)"
when /^15\.?/ then "macOS Sequoia (15)"
when /Ubuntu(-Server)? (14|16|18|20|22)\.04/ then "Ubuntu #{Regexp.last_match(2)}.04 LTS"
when /Ubuntu(-Server)? (\d+\.\d+).\d ?(LTS)?/
"Ubuntu #{Regexp.last_match(2)} #{Regexp.last_match(3)}".strip
when %r{Debian GNU/Linux (\d+)\.\d+} then "Debian #{Regexp.last_match(1)} #{Regexp.last_match(2)}"
when /CentOS (\w+) (\d+)/ then "CentOS #{Regexp.last_match(1)} #{Regexp.last_match(2)}"
when /Fedora Linux (\d+)[.\d]*/ then "Fedora Linux #{Regexp.last_match(1)}"
when /KDE neon .*?([\d.]+)/ then "KDE neon #{Regexp.last_match(1)}"
when /Amazon Linux (\d+)\.[.\d]*/ then "Amazon Linux #{Regexp.last_match(1)}"
else dimension
end
end
end
end
end

View File

@ -0,0 +1,138 @@
# typed: strict
# frozen_string_literal: true
require "abstract_command"
module Homebrew
module DevCmd
class GenerateAnalyticsApi < AbstractCommand
CATEGORIES = %w[
build-error install install-on-request
core-build-error core-install core-install-on-request
cask-install core-cask-install os-version
homebrew-devcmdrun-developer homebrew-os-arch-ci
homebrew-prefixes homebrew-versions
brew-command-run brew-command-run-options brew-test-bot-test
].freeze
# TODO: add brew-command-run-options brew-test-bot-test to above when working.
DAYS = %w[30 90 365].freeze
MAX_RETRIES = 3
cmd_args do
description <<~EOS
Generates analytics API data files for formulae.brew.sh.
The generated files are written to the current directory.
EOS
named_args :none
end
sig { params(category_name: String, data_source: T.nilable(String)).returns(String) }
def analytics_json_template(category_name, data_source: nil)
data_source = "#{data_source}: true" if data_source
<<~EOS
---
layout: analytics_json
category: #{category_name}
#{data_source}
---
{{ content }}
EOS
end
sig { params(args: String).returns(String) }
def run_formula_analytics(*args)
puts "brew formula-analytics #{args.join(" ")}"
retries = 0
result = Utils.popen_read(HOMEBREW_BREW_FILE, "formula-analytics", *args, err: :err)
while !$CHILD_STATUS.success? && retries < MAX_RETRIES
# Give InfluxDB some more breathing room.
sleep 4**(retries+2)
retries += 1
puts "Retrying #{args.join(" ")} (#{retries}/#{MAX_RETRIES})..."
result = Utils.popen_read(HOMEBREW_BREW_FILE, "formula-analytics", *args, err: :err)
end
odie "`brew formula-analytics #{args.join(" ")}` failed: #{result}" unless $CHILD_STATUS.success?
result
end
sig { override.void }
def run
safe_system HOMEBREW_BREW_FILE, "formula-analytics", "--setup"
directories = ["_data/analytics", "api/analytics"]
FileUtils.rm_rf directories
FileUtils.mkdir_p directories
root_dir = Pathname.pwd
analytics_data_dir = root_dir/"_data/analytics"
analytics_api_dir = root_dir/"api/analytics"
threads = []
CATEGORIES.each do |category|
formula_analytics_args = []
case category
when "core-build-error"
formula_analytics_args << "--all-core-formulae-json"
formula_analytics_args << "--build-error"
category_name = "build-error"
data_source = "homebrew-core"
when "core-install"
formula_analytics_args << "--all-core-formulae-json"
formula_analytics_args << "--install"
category_name = "install"
data_source = "homebrew-core"
when "core-install-on-request"
formula_analytics_args << "--all-core-formulae-json"
formula_analytics_args << "--install-on-request"
category_name = "install-on-request"
data_source = "homebrew-core"
when "core-cask-install"
formula_analytics_args << "--all-core-formulae-json"
formula_analytics_args << "--cask-install"
category_name = "cask-install"
data_source = "homebrew-cask"
else
formula_analytics_args << "--#{category}"
category_name = category
end
path_suffix = File.join(category_name, data_source || "")
analytics_data_path = analytics_data_dir/path_suffix
analytics_api_path = analytics_api_dir/path_suffix
FileUtils.mkdir_p analytics_data_path
FileUtils.mkdir_p analytics_api_path
# The `--json` and `--all-core-formulae-json` flags are mutually
# exclusive, but we need to explicitly set `--json` sometimes,
# so only set it if we've not already set
# `--all-core-formulae-json`.
formula_analytics_args << "--json" unless formula_analytics_args.include? "--all-core-formulae-json"
DAYS.each do |days|
next if days != "30" && category_name == "build-error" && !data_source.nil?
threads << Thread.new do
args = %W[--days-ago=#{days}]
(analytics_data_path/"#{days}d.json").write run_formula_analytics(*formula_analytics_args, *args)
(analytics_api_path/"#{days}d.json").write analytics_json_template(category_name, data_source:)
end
end
end
threads.each(&:join)
end
end
end
end

View File

@ -0,0 +1 @@
3.12

View File

@ -0,0 +1,12 @@
# typed: strict
# frozen_string_literal: true
require "pycall/import"
# This was a rewrite from `include(Module.new(...))`,
# to appease Sorbet, so let's keep the existing behaviour
# and silence RuboCop.
# rubocop:disable Style/MixinUsage
include PyCall::Import
# rubocop:enable Style/MixinUsage
pyfrom "influxdb_client_3", import: :InfluxDBClient3

View File

@ -0,0 +1,24 @@
# typed: strict
class InfluxDBClient3
def self.initialize(*args); end
def query(*args); end
end
module PyCall
def self.init(*args); end
module Import
def self.pyfrom(*args); end
def self.import(*args); end
end
PyError = Class.new(StandardError).freeze
end
# Needs defined here for Sorbet to work as expected.
# rubocop:disable Style/TopLevelMethodDefinition
def pyfrom(*args); end
# rubocop:enable Style/TopLevelMethodDefinition

View File

@ -0,0 +1 @@
influxdb3-python

View File

@ -0,0 +1,84 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
#
certifi==2025.1.31 \
--hash=sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651 \
--hash=sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe
# via influxdb3-python
influxdb3-python==0.10.0 \
--hash=sha256:d279e5f8a597d49b44035263b1cf1472a3861ceba930fd08e1e3b1721a07d3cf \
--hash=sha256:f3d44dff4c4bbfdcb1fa1c4013ccfa317fbbd7df5812eb46395421166ffb385a
# via -r requirements.in
pyarrow==19.0.0 \
--hash=sha256:239ca66d9a05844bdf5af128861af525e14df3c9591bcc05bac25918e650d3a2 \
--hash=sha256:2795064647add0f16563e57e3d294dbfc067b723f0fd82ecd80af56dad15f503 \
--hash=sha256:29cd86c8001a94f768f79440bf83fee23963af5e7bc68ce3a7e5f120e17edf89 \
--hash=sha256:2a0144a712d990d60f7f42b7a31f0acaccf4c1e43e957f7b1ad58150d6f639c1 \
--hash=sha256:2a1a109dfda558eb011e5f6385837daffd920d54ca00669f7a11132d0b1e6042 \
--hash=sha256:2b6d3ce4288793350dc2d08d1e184fd70631ea22a4ff9ea5c4ff182130249d9b \
--hash=sha256:2f672f5364b2d7829ef7c94be199bb88bf5661dd485e21d2d37de12ccb78a136 \
--hash=sha256:3c1c162c4660e0978411a4761f91113dde8da3433683efa473501254563dcbe8 \
--hash=sha256:450a7d27e840e4d9a384b5c77199d489b401529e75a3b7a3799d4cd7957f2f9c \
--hash=sha256:4624c89d6f777c580e8732c27bb8e77fd1433b89707f17c04af7635dd9638351 \
--hash=sha256:4d8b0c0de0a73df1f1bf439af1b60f273d719d70648e898bc077547649bb8352 \
--hash=sha256:5418d4d0fab3a0ed497bad21d17a7973aad336d66ad4932a3f5f7480d4ca0c04 \
--hash=sha256:597360ffc71fc8cceea1aec1fb60cb510571a744fffc87db33d551d5de919bec \
--hash=sha256:5e8a28b918e2e878c918f6d89137386c06fe577cd08d73a6be8dafb317dc2d73 \
--hash=sha256:62ef8360ff256e960f57ce0299090fb86423afed5e46f18f1225f960e05aae3d \
--hash=sha256:66732e39eaa2247996a6b04c8aa33e3503d351831424cdf8d2e9a0582ac54b34 \
--hash=sha256:718947fb6d82409013a74b176bf93e0f49ef952d8a2ecd068fecd192a97885b7 \
--hash=sha256:8d47c691765cf497aaeed4954d226568563f1b3b74ff61139f2d77876717084b \
--hash=sha256:8e3a839bf36ec03b4315dc924d36dcde5444a50066f1c10f8290293c0427b46a \
--hash=sha256:9348a0137568c45601b031a8d118275069435f151cbb77e6a08a27e8125f59d4 \
--hash=sha256:a08e2a8a039a3f72afb67a6668180f09fddaa38fe0d21f13212b4aba4b5d2451 \
--hash=sha256:a218670b26fb1bc74796458d97bcab072765f9b524f95b2fccad70158feb8b17 \
--hash=sha256:a22a4bc0937856263df8b94f2f2781b33dd7f876f787ed746608e06902d691a5 \
--hash=sha256:a7bbe7109ab6198688b7079cbad5a8c22de4d47c4880d8e4847520a83b0d1b68 \
--hash=sha256:a92aff08e23d281c69835e4a47b80569242a504095ef6a6223c1f6bb8883431d \
--hash=sha256:b34d3bde38eba66190b215bae441646330f8e9da05c29e4b5dd3e41bde701098 \
--hash=sha256:b903afaa5df66d50fc38672ad095806443b05f202c792694f3a604ead7c6ea6e \
--hash=sha256:be686bf625aa7b9bada18defb3a3ea3981c1099697239788ff111d87f04cd263 \
--hash=sha256:c0423393e4a07ff6fea08feb44153302dd261d0551cc3b538ea7a5dc853af43a \
--hash=sha256:c318eda14f6627966997a7d8c374a87d084a94e4e38e9abbe97395c215830e0c \
--hash=sha256:c3b78eff5968a1889a0f3bc81ca57e1e19b75f664d9c61a42a604bf9d8402aae \
--hash=sha256:c73268cf557e688efb60f1ccbc7376f7e18cd8e2acae9e663e98b194c40c1a2d \
--hash=sha256:c751c1c93955b7a84c06794df46f1cec93e18610dcd5ab7d08e89a81df70a849 \
--hash=sha256:ce42275097512d9e4e4a39aade58ef2b3798a93aa3026566b7892177c266f735 \
--hash=sha256:cf3bf0ce511b833f7bc5f5bb3127ba731e97222023a444b7359f3a22e2a3b463 \
--hash=sha256:da410b70a7ab8eb524112f037a7a35da7128b33d484f7671a264a4c224ac131d \
--hash=sha256:e675a3ad4732b92d72e4d24009707e923cab76b0d088e5054914f11a797ebe44 \
--hash=sha256:e82c3d5e44e969c217827b780ed8faf7ac4c53f934ae9238872e749fa531f7c9 \
--hash=sha256:edfe6d3916e915ada9acc4e48f6dafca7efdbad2e6283db6fd9385a1b23055f1 \
--hash=sha256:f094742275586cdd6b1a03655ccff3b24b2610c3af76f810356c4c71d24a2a6c \
--hash=sha256:f208c3b58a6df3b239e0bb130e13bc7487ed14f39a9ff357b6415e3f6339b560 \
--hash=sha256:f43f5aef2a13d4d56adadae5720d1fed4c1356c993eda8b59dace4b5983843c1
# via influxdb3-python
python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
# via influxdb3-python
reactivex==4.0.4 \
--hash=sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a \
--hash=sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8
# via influxdb3-python
six==1.17.0 \
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
# via python-dateutil
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via reactivex
urllib3==2.3.0 \
--hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \
--hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d
# via influxdb3-python
# The following packages are considered to be unsafe in a requirements file:
setuptools==75.8.0 \
--hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \
--hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3
# via influxdb3-python

File diff suppressed because one or more lines are too long

View File

@ -4216,7 +4216,7 @@ class Formula
lambda do |_| lambda do |_|
on_macos do on_macos do
T.bind(self, PourBottleCheck) T.bind(self, PourBottleCheck)
reason(<<~EOS) reason(+<<~EOS)
The bottle needs the Xcode Command Line Tools to be installed at /Library/Developer/CommandLineTools. The bottle needs the Xcode Command Line Tools to be installed at /Library/Developer/CommandLineTools.
Development tools provided by Xcode.app are not sufficient. Development tools provided by Xcode.app are not sufficient.

View File

@ -12,8 +12,6 @@ module Homebrew
module Strategy module Strategy
extend Utils::Curl extend Utils::Curl
module_function
# {Strategy} priorities informally range from 1 to 10, where 10 is the # {Strategy} priorities informally range from 1 to 10, where 10 is the
# highest priority. 5 is the default priority because it's roughly in # highest priority. 5 is the default priority because it's roughly in
# the middle of this range. Strategies with a priority of 0 (or lower) # the middle of this range. Strategies with a priority of 0 (or lower)
@ -98,7 +96,7 @@ module Homebrew
# loaded, otherwise livecheck won't be able to use them. # loaded, otherwise livecheck won't be able to use them.
# @return [Hash] # @return [Hash]
sig { returns(T::Hash[Symbol, T.untyped]) } sig { returns(T::Hash[Symbol, T.untyped]) }
def strategies def self.strategies
@strategies ||= T.let(Strategy.constants.sort.each_with_object({}) do |const_symbol, hash| @strategies ||= T.let(Strategy.constants.sort.each_with_object({}) do |const_symbol, hash|
constant = Strategy.const_get(const_symbol) constant = Strategy.const_get(const_symbol)
next unless constant.is_a?(Class) next unless constant.is_a?(Class)
@ -116,7 +114,7 @@ module Homebrew
# `Symbol` (e.g. `:page_match`) # `Symbol` (e.g. `:page_match`)
# @return [Class, nil] # @return [Class, nil]
sig { params(symbol: T.nilable(Symbol)).returns(T.untyped) } sig { params(symbol: T.nilable(Symbol)).returns(T.untyped) }
def from_symbol(symbol) def self.from_symbol(symbol)
strategies[symbol] if symbol.present? strategies[symbol] if symbol.present?
end end
@ -138,7 +136,7 @@ module Homebrew
block_provided: T::Boolean, block_provided: T::Boolean,
).returns(T::Array[T.untyped]) ).returns(T::Array[T.untyped])
} }
def from_url(url, livecheck_strategy: nil, regex_provided: false, block_provided: false) def self.from_url(url, livecheck_strategy: nil, regex_provided: false, block_provided: false)
usable_strategies = strategies.select do |strategy_symbol, strategy| usable_strategies = strategies.select do |strategy_symbol, strategy|
if strategy == PageMatch if strategy == PageMatch
# Only treat the strategy as usable if the `livecheck` block # Only treat the strategy as usable if the `livecheck` block
@ -178,7 +176,7 @@ module Homebrew
post_json: T.nilable(T::Hash[T.any(String, Symbol), String]), post_json: T.nilable(T::Hash[T.any(String, Symbol), String]),
).returns(T::Array[String]) ).returns(T::Array[String])
} }
def post_args(post_form: nil, post_json: nil) def self.post_args(post_form: nil, post_json: nil)
if post_form.present? if post_form.present?
require "uri" require "uri"
["--data", URI.encode_www_form(post_form)] ["--data", URI.encode_www_form(post_form)]

View File

@ -17,6 +17,9 @@ class Homebrew::Cmd::Reinstall::Args < Homebrew::CLI::Args
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def appdir; end def appdir; end
sig { returns(T::Boolean) }
def ask?; end
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def audio_unit_plugindir; end def audio_unit_plugindir; end

View File

@ -14,6 +14,9 @@ class Homebrew::Cmd::UpgradeCmd::Args < Homebrew::CLI::Args
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def appdir; end def appdir; end
sig { returns(T::Boolean) }
def ask?; end
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def audio_unit_plugindir; end def audio_unit_plugindir; end

View File

@ -0,0 +1,61 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `Homebrew::DevCmd::FormulaAnalytics`.
# Please instead update this file by running `bin/tapioca dsl Homebrew::DevCmd::FormulaAnalytics`.
class Homebrew::DevCmd::FormulaAnalytics
sig { returns(Homebrew::DevCmd::FormulaAnalytics::Args) }
def args; end
end
class Homebrew::DevCmd::FormulaAnalytics::Args < Homebrew::CLI::Args
sig { returns(T::Boolean) }
def all_core_formulae_json?; end
sig { returns(T::Boolean) }
def brew_command_run?; end
sig { returns(T::Boolean) }
def brew_command_run_options?; end
sig { returns(T::Boolean) }
def brew_test_bot_test?; end
sig { returns(T::Boolean) }
def build_error?; end
sig { returns(T::Boolean) }
def cask_install?; end
sig { returns(T.nilable(String)) }
def days_ago; end
sig { returns(T::Boolean) }
def homebrew_devcmdrun_developer?; end
sig { returns(T::Boolean) }
def homebrew_os_arch_ci?; end
sig { returns(T::Boolean) }
def homebrew_prefixes?; end
sig { returns(T::Boolean) }
def homebrew_versions?; end
sig { returns(T::Boolean) }
def install?; end
sig { returns(T::Boolean) }
def install_on_request?; end
sig { returns(T::Boolean) }
def json?; end
sig { returns(T::Boolean) }
def os_version?; end
sig { returns(T::Boolean) }
def setup?; end
end

View File

@ -0,0 +1,13 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `Homebrew::DevCmd::GenerateAnalyticsApi`.
# Please instead update this file by running `bin/tapioca dsl Homebrew::DevCmd::GenerateAnalyticsApi`.
class Homebrew::DevCmd::GenerateAnalyticsApi
sig { returns(Homebrew::DevCmd::GenerateAnalyticsApi::Args) }
def args; end
end
class Homebrew::DevCmd::GenerateAnalyticsApi::Args < Homebrew::CLI::Args; end

View File

@ -28,6 +28,9 @@ module Homebrew::EnvConfig
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def artifact_domain_no_fallback?; end def artifact_domain_no_fallback?; end
sig { returns(T::Boolean) }
def ask?; end
sig { returns(T.nilable(::String)) } sig { returns(T.nilable(::String)) }
def auto_update_secs; end def auto_update_secs; end

View File

@ -35,4 +35,7 @@ gem:
- unicode-display_width - unicode-display_width
- unicode-emoji - unicode-emoji
- yard-sorbet - yard-sorbet
# The tapioca generated gem is not correct or sufficient for pycall
# so we need to generate our own:
- pycall
prerequire: sorbet/tapioca/prerequire.rb prerequire: sorbet/tapioca/prerequire.rb

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "dev-cmd/formula-analytics"
RSpec.describe Homebrew::DevCmd::FormulaAnalytics do
it_behaves_like "parseable arguments"
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "dev-cmd/generate-analytics-api"
RSpec.describe Homebrew::DevCmd::GenerateAnalyticsApi do
it_behaves_like "parseable arguments"
end

View File

@ -401,7 +401,8 @@ module PyPI
end end
end end
resource_section = "#{package_errors}\n#{new_resource_blocks}" package_errors += "\n" if package_errors.present?
resource_section = "#{package_errors}#{new_resource_blocks}"
odie "Excluded superfluous packages: #{exclude_packages.join(", ")}" if exclude_packages.any? odie "Excluded superfluous packages: #{exclude_packages.join(", ")}" if exclude_packages.any?

View File

@ -71,8 +71,10 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/prism-1.3.0") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/prism-1.3.0")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/prism-1.3.0/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/prism-1.3.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/pry-0.15.2/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/pry-0.15.2/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/pycall-1.5.2")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/pycall-1.5.2/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rainbow-3.1.1/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rainbow-3.1.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-runtime-0.5.11805/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-runtime-0.5.11812/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rbi-0.2.4/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rbi-0.2.4/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/rbs-3.8.1") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/rbs-3.8.1")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rbs-3.8.1/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rbs-3.8.1/lib")
@ -96,7 +98,7 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-performance-1.23.1/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-performance-1.23.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rspec-3.4.0/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rspec-3.4.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-sorbet-0.8.7/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-sorbet-0.8.7/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-lsp-0.23.8/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-lsp-0.23.9/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-macho-4.1.0/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-macho-4.1.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/ruby-prof-1.7.1") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/ruby-prof-1.7.1")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-prof-1.7.1/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-prof-1.7.1/lib")
@ -104,9 +106,9 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov_json_formatter-0.1.4/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov_json_formatter-0.1.4/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-0.22.0/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-0.22.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-cobertura-2.1.0/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-cobertura-2.1.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-static-0.5.11805-universal-darwin/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-static-0.5.11812-universal-darwin/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-0.5.11805/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-0.5.11812/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-static-and-runtime-0.5.11805/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-static-and-runtime-0.5.11812/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/thor-1.3.2/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/thor-1.3.2/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/spoom-1.5.3/lib") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/spoom-1.5.3/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/stackprof-0.2.27") $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/stackprof-0.2.27")

Some files were not shown because too many files have changed in this diff Show More