diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index eee0e3d9e4..e66e14c3a5 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -1245,15 +1245,17 @@ module Cask url.sub(%r{^[^:/]+://(www\.)?}, "") end - sig { returns(String) } + sig { returns(T.nilable(String)) } def url_from_verified - strip_url_scheme(T.must(cask.url).verified) + return unless (verified_url = T.must(cask.url).verified) + + strip_url_scheme(verified_url) end sig { returns(T::Boolean) } def verified_matches_url? url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2) - verified_domain, verified_path = url_from_verified.split("/", 2) + verified_domain, verified_path = url_from_verified&.split("/", 2) domains_match = (url_domain == verified_domain) || (verified_domain && url_domain&.end_with?(".#{verified_domain}")) diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb index 04174e9809..6dac4e2585 100644 --- a/Library/Homebrew/cask/dsl.rb +++ b/Library/Homebrew/cask/dsl.rb @@ -314,15 +314,11 @@ module Cask # ``` # # @api public - def url(*args, **options, &block) + def url(*args, **options) caller_location = T.must(caller_locations).fetch(0) - set_unique_stanza(:url, args.empty? && options.empty? && !block) do - if block - URL.new(*args, **options, caller_location:, dsl: self, &block) - else - URL.new(*args, **options, caller_location:) - end + set_unique_stanza(:url, args.empty? && options.empty?) do + URL.new(*args, **options, caller_location:) end end diff --git a/Library/Homebrew/cask/url.rb b/Library/Homebrew/cask/url.rb index 307f0455aa..71489fcdde 100644 --- a/Library/Homebrew/cask/url.rb +++ b/Library/Homebrew/cask/url.rb @@ -6,94 +6,47 @@ require "utils/curl" module Cask # Class corresponding to the `url` stanza. - class URL < SimpleDelegator - # Methods for the `url` stanza. - class DSL - sig { returns(T.any(URI::Generic, String)) } - attr_reader :uri + class URL + sig { returns(URI::Generic) } + attr_reader :uri - sig { returns(T.nilable(T::Hash[T.any(Symbol, String), String])) } - attr_reader :revisions + sig { returns(T.nilable(T::Hash[T.any(Symbol, String), String])) } + attr_reader :revisions - sig { returns(T.nilable(T::Boolean)) } - attr_reader :trust_cert + sig { returns(T.nilable(T::Boolean)) } + attr_reader :trust_cert - sig { returns(T.nilable(T::Hash[String, String])) } - attr_reader :cookies, :data + sig { returns(T.nilable(T::Hash[String, String])) } + attr_reader :cookies, :data - sig { returns(T.nilable(T.any(String, T::Array[String]))) } - attr_reader :header + sig { returns(T.nilable(T.any(String, T::Array[String]))) } + attr_reader :header - sig { returns(T.nilable(T.any(URI::Generic, String))) } - attr_reader :referer + sig { returns(T.nilable(T.any(URI::Generic, String))) } + attr_reader :referer - sig { returns(T::Hash[Symbol, T.untyped]) } - attr_reader :specs + sig { returns(T::Hash[Symbol, T.untyped]) } + attr_reader :specs - sig { returns(T.nilable(T.any(Symbol, String))) } - attr_reader :user_agent + sig { returns(T.nilable(T.any(Symbol, String))) } + attr_reader :user_agent - sig { returns(T.any(T::Class[AbstractDownloadStrategy], Symbol, NilClass)) } - attr_reader :using + sig { returns(T.any(T::Class[AbstractDownloadStrategy], Symbol, NilClass)) } + attr_reader :using - sig { returns(T.nilable(String)) } - attr_reader :tag, :branch, :revision, :only_path, :verified + sig { returns(T.nilable(String)) } + attr_reader :tag, :branch, :revision, :only_path, :verified - extend Forwardable + extend Forwardable - def_delegators :uri, :path, :scheme, :to_s - - # Creates a `url` stanza. - # - # @api public - sig { - params( - uri: T.any(URI::Generic, String), - verified: T.nilable(String), - using: T.any(T::Class[AbstractDownloadStrategy], Symbol, NilClass), - tag: T.nilable(String), - branch: T.nilable(String), - revisions: T.nilable(T::Hash[T.any(Symbol, String), String]), - revision: T.nilable(String), - trust_cert: T.nilable(T::Boolean), - cookies: T.nilable(T::Hash[String, String]), - referer: T.nilable(T.any(URI::Generic, String)), - header: T.nilable(T.any(String, T::Array[String])), - user_agent: T.nilable(T.any(Symbol, String)), - data: T.nilable(T::Hash[String, String]), - only_path: T.nilable(String), - ).void - } - def initialize( - uri, verified: nil, using: nil, tag: nil, branch: nil, revisions: nil, revision: nil, trust_cert: nil, - cookies: nil, referer: nil, header: nil, user_agent: nil, data: nil, only_path: nil - ) - @uri = T.let(URI(uri), T.any(URI::Generic, String)) - - header = Array(header) unless header.nil? - - specs = {} - specs[:verified] = @verified = T.let(verified, T.nilable(String)) - specs[:using] = @using = T.let(using, T.any(T::Class[AbstractDownloadStrategy], Symbol, NilClass)) - specs[:tag] = @tag = T.let(tag, T.nilable(String)) - specs[:branch] = @branch = T.let(branch, T.nilable(String)) - specs[:revisions] = @revisions = T.let(revisions, T.nilable(T::Hash[T.any(Symbol, String), String])) - specs[:revision] = @revision = T.let(revision, T.nilable(String)) - specs[:trust_cert] = @trust_cert = T.let(trust_cert, T.nilable(T::Boolean)) - specs[:cookies] = @cookies = T.let(cookies, T.nilable(T::Hash[String, String])) - specs[:referer] = @referer = T.let(referer, T.nilable(T.any(URI::Generic, String))) - specs[:headers] = @header = T.let(header, T.nilable(T.any(String, T::Array[String]))) - specs[:user_agent] = @user_agent = T.let(user_agent || :default, T.nilable(T.any(Symbol, String))) - specs[:data] = @data = T.let(data, T.nilable(T::Hash[String, String])) - specs[:only_path] = @only_path = T.let(only_path, T.nilable(String)) - - @specs = T.let(specs.compact, T::Hash[Symbol, T.untyped]) - end - end + def_delegators :uri, :path, :scheme, :to_s + # Creates a `url` stanza. + # + # @api public sig { params( - uri: T.nilable(T.any(URI::Generic, String)), + uri: T.any(URI::Generic, String), verified: T.nilable(String), using: T.any(T::Class[AbstractDownloadStrategy], Symbol, NilClass), tag: T.nilable(String), @@ -108,19 +61,33 @@ module Cask data: T.nilable(T::Hash[String, String]), only_path: T.nilable(String), caller_location: Thread::Backtrace::Location, - dsl: T.nilable(::Cask::DSL), ).void } def initialize( - uri = nil, verified: nil, using: nil, tag: nil, branch: nil, revisions: nil, revision: nil, trust_cert: nil, + uri, verified: nil, using: nil, tag: nil, branch: nil, revisions: nil, revision: nil, trust_cert: nil, cookies: nil, referer: nil, header: nil, user_agent: nil, data: nil, only_path: nil, - caller_location: caller_locations.fetch(0), dsl: nil + caller_location: caller_locations.fetch(0) ) - super( - DSL.new(T.must(uri), verified:, using:, tag:, branch:, revisions:, revision:, trust_cert:, cookies:, - referer:, header:, user_agent:, data:, only_path:) - ) + @uri = T.let(URI(uri), URI::Generic) + header = Array(header) unless header.nil? + + specs = {} + specs[:verified] = @verified = T.let(verified, T.nilable(String)) + specs[:using] = @using = T.let(using, T.any(T::Class[AbstractDownloadStrategy], Symbol, NilClass)) + specs[:tag] = @tag = T.let(tag, T.nilable(String)) + specs[:branch] = @branch = T.let(branch, T.nilable(String)) + specs[:revisions] = @revisions = T.let(revisions, T.nilable(T::Hash[T.any(Symbol, String), String])) + specs[:revision] = @revision = T.let(revision, T.nilable(String)) + specs[:trust_cert] = @trust_cert = T.let(trust_cert, T.nilable(T::Boolean)) + specs[:cookies] = @cookies = T.let(cookies, T.nilable(T::Hash[String, String])) + specs[:referer] = @referer = T.let(referer, T.nilable(T.any(URI::Generic, String))) + specs[:headers] = @header = T.let(header, T.nilable(T.any(String, T::Array[String]))) + specs[:user_agent] = @user_agent = T.let(user_agent || :default, T.nilable(T.any(Symbol, String))) + specs[:data] = @data = T.let(data, T.nilable(T::Hash[String, String])) + specs[:only_path] = @only_path = T.let(only_path, T.nilable(String)) + + @specs = T.let(specs.compact, T::Hash[Symbol, T.untyped]) @caller_location = T.let(caller_location, Thread::Backtrace::Location) end diff --git a/Library/Homebrew/sorbet/rbi/dsl/cask/url.rbi b/Library/Homebrew/sorbet/rbi/dsl/cask/url.rbi index f2c9c8a2ca..e43274d5a8 100644 --- a/Library/Homebrew/sorbet/rbi/dsl/cask/url.rbi +++ b/Library/Homebrew/sorbet/rbi/dsl/cask/url.rbi @@ -6,59 +6,12 @@ class Cask::URL - include Kernel + sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) } + def path(*args, &block); end - sig { returns(T.untyped) } - def branch; end + sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) } + def scheme(*args, &block); end - sig { returns(T.nilable(T::Hash[::String, ::String])) } - def cookies; end - - sig { returns(T.untyped) } - def data; end - - sig { returns(T.nilable(T.any(::String, T::Array[::String]))) } - def header; end - - sig { returns(T.untyped) } - def only_path; end - - sig { returns(T.untyped) } - def path; end - - sig { returns(T.nilable(T.any(::String, ::URI::Generic))) } - def referer; end - - sig { returns(T.untyped) } - def revision; end - - sig { returns(T.nilable(T::Hash[T.any(::String, ::Symbol), ::String])) } - def revisions; end - - sig { returns(T.untyped) } - def scheme; end - - sig { returns(T::Hash[::Symbol, T.untyped]) } - def specs; end - - sig { returns(T.nilable(::String)) } - def tag; end - - sig { returns(T.untyped) } - def to_s; end - - sig { returns(T.nilable(T::Boolean)) } - def trust_cert; end - - sig { returns(T.any(::String, ::URI::Generic)) } - def uri; end - - sig { returns(T.nilable(T.any(::String, ::Symbol))) } - def user_agent; end - - sig { returns(T.nilable(T.any(::Symbol, T::Class[::AbstractDownloadStrategy]))) } - def using; end - - sig { returns(T.untyped) } - def verified; end + sig { params(args: T.untyped, block: T.untyped).returns(String) } + def to_s(*args, &block); end end diff --git a/Library/Homebrew/sorbet/rbi/dsl/cask/url/dsl.rbi b/Library/Homebrew/sorbet/rbi/dsl/cask/url/dsl.rbi deleted file mode 100644 index 4ae15d814f..0000000000 --- a/Library/Homebrew/sorbet/rbi/dsl/cask/url/dsl.rbi +++ /dev/null @@ -1,17 +0,0 @@ -# typed: true - -# DO NOT EDIT MANUALLY -# This is an autogenerated file for dynamic methods in `Cask::URL::DSL`. -# Please instead update this file by running `bin/tapioca dsl Cask::URL::DSL`. - - -class Cask::URL::DSL - sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) } - def path(*args, &block); end - - sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) } - def scheme(*args, &block); end - - sig { params(args: T.untyped, block: T.untyped).returns(String) } - def to_s(*args, &block); end -end diff --git a/Library/Homebrew/sorbet/tapioca/compilers/delegators.rb b/Library/Homebrew/sorbet/tapioca/compilers/delegators.rb deleted file mode 100644 index 14d1c359e8..0000000000 --- a/Library/Homebrew/sorbet/tapioca/compilers/delegators.rb +++ /dev/null @@ -1,40 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -require_relative "../../../global" -require "cask/url" - -module Tapioca - module Compilers - # A compiler for subclasses of Delegator. - # To add a new delegator: require it above and add it to the DELEGATIONS hash below. - class Delegators < Tapioca::Dsl::Compiler - # Mapping of delegator classes to the classes they delegate to (as defined in `__getobj__`). - DELEGATIONS = T.let({ - Cask::URL => Cask::URL::DSL, - }.freeze, T::Hash[Module, Module]) - ConstantType = type_member { { fixed: Module } } - - sig { override.returns(T::Enumerable[Module]) } - def self.gather_constants = DELEGATIONS.keys - - sig { override.void } - def decorate - root.create_path(constant) do |klass| - # Note that `Delegtor` does not subclass `Object`: - # https://github.com/ruby/ruby/blob/a6383fb/lib/delegate.rb#L41 - # but we assume that we are delegating to a class that does. - klass.create_include("Kernel") - delegated = DELEGATIONS.fetch(constant) - - delegated.instance_methods(false).each do |method| - signature = T::Utils.signature_for_method(delegated.instance_method(method)) - # TODO: handle methods with parameters - return_type = signature&.return_type&.to_s || "T.untyped" - klass.create_method(method.to_s, return_type:) - end - end - end - end - end -end diff --git a/docs/Adding-Software-to-Homebrew.md b/docs/Adding-Software-to-Homebrew.md index 6fc335694f..f6cb873c51 100644 --- a/docs/Adding-Software-to-Homebrew.md +++ b/docs/Adding-Software-to-Homebrew.md @@ -185,7 +185,7 @@ Fill in the following stanzas for your cask: | ------------------ | ----------- | | `version` | application version | | `sha256` | SHA-256 checksum of the file downloaded from `url`, calculated by the command `shasum -a 256 `. Can be suppressed by using the special value `:no_check`. (see [`sha256` Stanza Details](Cask-Cookbook.md#stanza-sha256)) | -| `url` | URL to the `.dmg`/`.zip`/`.tgz`/`.tbz2` file that contains the application.
A [`verified` parameter](Cask-Cookbook.md#when-url-and-homepage-domains-differ-add-verified) must be added if the hostnames in the `url` and `homepage` stanzas differ. [Block syntax](Cask-Cookbook.md#using-a-block-to-defer-code-execution) is available for URLs that change on every visit. | +| `url` | URL to the `.dmg`/`.zip`/`.tgz`/`.tbz2` file that contains the application.
A [`verified` parameter](Cask-Cookbook.md#when-url-and-homepage-domains-differ-add-verified) must be added if the hostnames in the `url` and `homepage` stanzas differ. | | `name` | the full and proper name defined by the vendor, and any useful alternate names (see [`name` Stanza Details](Cask-Cookbook.md#stanza-name)) | | `desc` | one-line description of the software (see [`desc` Stanza Details](Cask-Cookbook.md#stanza-desc)) | | `homepage` | application homepage; used for the `brew home` command | diff --git a/docs/Cask-Cookbook.md b/docs/Cask-Cookbook.md index 49b1015037..468f222e63 100644 --- a/docs/Cask-Cookbook.md +++ b/docs/Cask-Cookbook.md @@ -1070,46 +1070,7 @@ If these formats are not available, and the application is macOS-exclusive (othe Some hosting providers actively block command-line HTTP clients. Such URLs cannot be used in casks. -Other providers may use URLs that change periodically, or even on each visit (example: FossHub). While some cases [could be circumvented](#using-a-block-to-defer-code-execution), they tend to occur when the vendor is actively trying to prevent automated downloads, so we prefer to not add those casks to the main repository. - -#### Using a block to defer code execution - -Some applications—notably nightlies—have versioned download URLs that are updated so often that they become impractical to keep current with the usual process. For those, we want to dynamically determine `url`. - -**Note:** Casks with a dynamically-determined `url` are not allowed in Homebrew/homebrew-cask as they interfere with API generation. - -##### The Problem - -In theory, one can write arbitrary Ruby code right in the cask definition to fetch and construct a disposable URL. - -However, this typically involves an HTTP round trip to a landing site, which may take a long time. Because of the way Homebrew Cask loads and parses casks, it is not acceptable that such expensive operations be performed directly in the body of a cask definition. - -##### Writing the block - -Similar to the `preflight`, `postflight`, `uninstall_preflight` and `uninstall_postflight` blocks, the `url` stanza offers an optional *block syntax*: - -```ruby -url "https://handbrake.fr/nightly.php" do |page| - file_path = page[/href=["']?([^"' >]*Handbrake[._-][^"' >]+\.dmg)["' >]/i, 1] - file_path ? URI.join(page.url, file_path) : nil -end -``` - -You can also nest `url do` blocks inside `url do` blocks to follow a chain of URLs. - -The block is only evaluated when needed, for example at download time or when auditing a cask. Inside a block, you may safely do things such as HTTP(S) requests that may take a long time to execute. You may also refer to the `@cask` instance variable, and invoke [any method available on `@cask`](https://rubydoc.brew.sh/Cask/Cask). - -The block will be called immediately before downloading; its resulting value is assumed to be a `String` (or a pair of a `String` and `Hash` containing parameters) and subsequently used as a download URL. - -You can use the `url` stanza with either a direct argument or a block but not with both. - -Historical example of using block syntax: [vlc@nightly.rb](https://github.com/Homebrew/homebrew-cask/blob/0b2b76ad8c3fbf4e1ee2f5e758640c4963ad6aaf/Casks/v/vlc%40nightly.rb#L7-L12) - -##### Mixing additional URL parameters with block syntax - -In rare cases, you might need to set URL parameters like `cookies` or `referer` while also using block syntax. - -This is possible by returning a two-element array as a block result. The first element of the array must be the download URL; the second element must be a `Hash` containing the parameters. +Other providers may use URLs that change periodically, or even on each visit (example: FossHub). These cases tend to occur when the vendor is actively trying to prevent automated downloads, so we prefer to not add those casks to the main repository. ### Stanza: `version`