diff --git a/.github/workflows/command-not-found-db-update.yml b/.github/workflows/command-not-found-db-update.yml deleted file mode 100644 index 38f4a73ab9093..0000000000000 --- a/.github/workflows/command-not-found-db-update.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Command-not-found scheduled database updates - -on: - pull_request: - paths: - - .github/workflows/command-not-found-db-update.yml - - Library/Homebrew/dev-cmd/which-update.rb - - Library/Homebrew/executables_db.rb - schedule: - - cron: "0 0 * * *" - workflow_dispatch: - inputs: - max-downloads: - description: Maximum number of formulae to download when updating - required: false - -jobs: - update-database: - if: startsWith( github.repository, 'Homebrew/' ) - runs-on: macos-latest - permissions: - packages: write - steps: - - name: Set up Homebrew - id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@main - - - name: Install oras for interacting with GitHub Packages - uses: Homebrew/actions/cache-homebrew-prefix@main - with: - install: oras - workflow-key: command-not-found-db-update - uninstall: true - - - name: Pull executables.txt from GitHub Packages - run: oras pull ghcr.io/homebrew/command-not-found/executables:latest - - - name: Update database - env: - MAX_DOWNLOADS: ${{ github.event.inputs.max-downloads }} - run: | - set -eo pipefail - - if [[ -n "$MAX_DOWNLOADS" ]] - then - MAX_DOWNLOADS_ARGS="--max-downloads $MAX_DOWNLOADS" - fi - - # Need to intentionally leave MAX_DOWNLOADS_ARGS unquoted. - # shellcheck disable=SC2086 - brew which-update --update-existing --install-missing $MAX_DOWNLOADS_ARGS executables.txt --summary-file=$GITHUB_STEP_SUMMARY - - - name: Output database stats - run: brew which-update --stats executables.txt - - - name: Log in to GitHub Packages - if: github.ref == 'refs/heads/main' - run: echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io --username brewtestbot --password-stdin - - - name: Push to GitHub Packages - if: github.ref == 'refs/heads/main' - run: | - oras push --artifact-type application/vnd.homebrew.command-not-found.executables \ - ghcr.io/homebrew/command-not-found/executables:latest \ - executables.txt:text/plain - - - name: Check upload - if: github.ref == 'refs/heads/main' - run: | - shasum --algorithm=256 executables.txt > executables.txt.sha256 - rm -f executables.txt - oras pull ghcr.io/homebrew/command-not-found/executables:latest - shasum --algorithm=256 --check executables.txt.sha256 - - - name: Upload database artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: executables-database - path: executables.txt - - delete-old-versions: - needs: update-database - runs-on: ubuntu-slim - if: github.ref == 'refs/heads/main' - permissions: - packages: write - steps: - - name: Delete old versions from GitHub Packages - uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 - with: - package-name: command-not-found/executables - package-type: container - min-versions-to-keep: 0 - delete-only-untagged-versions: true diff --git a/Library/Homebrew/dev-cmd/which-entry.rb b/Library/Homebrew/dev-cmd/which-entry.rb new file mode 100644 index 0000000000000..15e9aa5c75537 --- /dev/null +++ b/Library/Homebrew/dev-cmd/which-entry.rb @@ -0,0 +1,69 @@ +# typed: strict +# frozen_string_literal: true + +require "abstract_command" +require "formula" +require "formulary" + +module Homebrew + module DevCmd + class WhichEntry < AbstractCommand + cmd_args do + description <<~EOS + Generate an `executables.txt` entry for using bottle manifest metadata. + EOS + flag "--output-db=", + description: "Append or update the entry in the given `executables.txt` database file." + named_args :formula, min: 1 + end + + sig { override.void } + def run + db_path = args.output_db + raise UsageError, "`--output-db` is required." unless db_path + + args.named.to_formulae.each { |formula| process(formula, db_path: Pathname(db_path)) } + end + + private + + sig { params(formula: Formula, db_path: Pathname).void } + def process(formula, db_path:) + line = db_line(formula) + write_db(db_path, formula.full_name, line) + end + + sig { params(db_path: Pathname, name: String, line: T.nilable(String)).void } + def write_db(db_path, name, line) + lines = db_path.readlines(chomp: true).compact_blank if db_path.exist? + # TODO: remove once pkg_versions are no longer in executables.txt + lines = Array(lines).reject { |l| l.start_with?(/#{Regexp.escape(name)}(?:\(.+\))?:/) } + lines << line if line + db_path.write("#{lines.sort.join("\n")}\n") + end + + sig { params(formula: Formula).returns(T.nilable(String)) } + def db_line(formula) + return if formula.disabled? || formula.deprecated? + + exes = executables_from_manifest(formula) + "#{formula.full_name}:#{exes.join(" ")}" + end + + sig { params(formula: Formula).returns(T::Array[String]) } + def executables_from_manifest(formula) + manifest_resource = formula.bottle&.github_packages_manifest_resource + return [] unless manifest_resource + + manifest_resource.fetch unless manifest_resource.downloaded? + manifest_path = manifest_resource.cached_download + return [] unless manifest_path.exist? + + manifest_resource.path_exec_files + rescue JSON::ParserError => e + opoo "Failed to parse bottle manifest for #{formula.name}: #{e.message}" + [] + end + end + end +end diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index 9d7a69e4db986..e4926b951b84a 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -411,6 +411,14 @@ def installed_size manifest_annotations["sh.brew.bottle.installed_size"]&.to_i end + sig { returns(T::Array[String]) } + def path_exec_files + exec_files = manifest_annotations["sh.brew.path_exec_files"] + return [] if exec_files.blank? + + exec_files.split.map { |f| File.basename(f) }.sort + end + sig { override.returns(String) } def download_queue_type = "Bottle Manifest" diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/which_entry.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/which_entry.rbi new file mode 100644 index 0000000000000..8a2954875970a --- /dev/null +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/which_entry.rbi @@ -0,0 +1,16 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Homebrew::DevCmd::WhichEntry`. +# Please instead update this file by running `bin/tapioca dsl Homebrew::DevCmd::WhichEntry`. + + +class Homebrew::DevCmd::WhichEntry + sig { returns(Homebrew::DevCmd::WhichEntry::Args) } + def args; end +end + +class Homebrew::DevCmd::WhichEntry::Args < Homebrew::CLI::Args + sig { returns(T.nilable(String)) } + def output_db; end +end diff --git a/Library/Homebrew/test/dev-cmd/which-entry_spec.rb b/Library/Homebrew/test/dev-cmd/which-entry_spec.rb new file mode 100644 index 0000000000000..8f256ed5e2d40 --- /dev/null +++ b/Library/Homebrew/test/dev-cmd/which-entry_spec.rb @@ -0,0 +1,15 @@ +# typed: false +# frozen_string_literal: true + +require "cmd/shared_examples/args_parse" +require "dev-cmd/which-entry" + +RSpec.describe Homebrew::DevCmd::WhichEntry do + it_behaves_like "parseable arguments" + + it "raises a UsageError if --output-db is not passed", :integration_test do + expect { brew "which-entry", "wget" } + .to output(/`--output-db` is required/).to_stderr + .and be_a_failure + end +end