Updates the documentation for PR https://github.com/Homebrew/homebrew-cask/pull/174292 that converted find-appcast from a developer script to command.
18 KiB
brew livecheck
The brew livecheck
command finds the newest version of a formula or cask's software by checking upstream. Livecheck has strategies to identify versions from various sources, such as Git repositories, websites, etc.
Behavior
When livecheck isn't given instructions for how to check for upstream versions, it does the following by default:
- For formulae: Collect the
stable
,head
, andhomepage
URLs, in that order (resources simply use theirurl
). For casks: Collect theurl
andhomepage
URLs, in that order. - Determine if any strategies apply to the first URL. If not, try the next URL.
- If a strategy can be applied, use it to check for new versions.
- Return the newest version (or an error if versions could not be found at any available URLs).
It's sometimes necessary to override this default behavior to create a working check. If a source doesn't provide the newest version, we need to check a different one. If livecheck doesn't correctly match version text, we need to provide an appropriate regex or strategy
block.
This can be accomplished by adding a livecheck
block to the formula/cask/resource. For more information on the available methods, please refer to the Livecheck
class documentation.
Creating a check
-
Use the debug output to understand the situation.
brew livecheck --debug <formula>|<cask>
provides information about which URLs livecheck tries, any strategies that apply, matched versions, etc. -
Research available sources to select a URL. Try removing the file name from
stable
/url
to see if it provides a directory listing page. If that doesn't work, try to find a page that links to the file (e.g. a download page). If it's not possible to find the newest version on the website, try checking other sources from the formula/cask. When necessary, search for other sources outside of the formula/cask. -
Create a regex, if necessary. If the check works without a regex and wouldn't benefit from having one, it's usually fine to omit it. More information on creating regexes can be found in the regex guidelines section.
General guidelines
-
Only use
strategy
when it's necessary. For example, if livecheck is already using theGit
strategy for a URL, it's not necessary to usestrategy :git
. However, ifGit
applies to a URL but we need to usePageMatch
, it's necessary to specifystrategy :page_match
. -
Only use the
GithubLatest
andGithubReleases
strategies when they are necessary and correct. GitHub rate-limits API requests, so we only use these strategies whenGit
isn't sufficient or appropriate.GithubLatest
should only be used if the upstream repository has a "latest" release for a suitable version and either the formula/cask uses a release asset or theGit
strategy can't correctly identify the latest release version.GithubReleases
should only be used if the upstream repository uses releases and both theGit
andGithubLatest
strategies aren't suitable.
URL guidelines
-
A
url
is required in alivecheck
block. This can be a URL string (e.g."https://www.example.com/downloads/"
) or a formula/cask URL symbol (i.e.:stable
,:url
,:head
,:homepage
). The exception to this rule is alivecheck
block that only usesskip
. -
Check for versions in the same location as the stable archive, whenever possible.
-
Avoid checking paginated release pages, when possible. For example, we generally avoid checking the
release
page for a GitHub project because the latest stable version can be pushed off the first page by pre-release versions. In this scenario, it's more reliable to use theGit
strategy, which fetches all the tags in the repository.
Regex guidelines
The livecheck
block regex restricts matches to a subset of the fetched content and uses a capture group around the version text.
-
Regexes should be made case insensitive, whenever possible, by adding
i
at the end (e.g./.../i
or%r{...}i
). This improves reliability, as the regex will handle changes in letter case without needing modifications. -
Regexes should only use a capturing group around the version text. For example, in
/href=.*?example-v?(\d+(?:\.\d+)+)(?:-src)?\.t/i
, we're only using a capturing group around the version test (matching a version like1.2
,1.2.3
, etc.) and we're using non-capturing groups elsewhere (e.g.(?:-src)?
). -
Anchor the start/end of the regex, to restrict the scope. For example, on HTML pages we often match file names or version directories in
href
attribute URLs (e.g./href=.*?example[._-]v?(\d+(?:\.\d+)+)\.zip/i
). The general idea is that limiting scope will help exclude unwanted matches. -
Avoid generic catchalls like
.*
or.+
in favor of something non-greedy and/or contextually appropriate. For example, to match characters within the bounds of an HTML attribute, use[^"' >]+?
. -
Use
[._-]
in place of a period/underscore/hyphen between the software name and version in a file name. For a file namedexample-1.2.3.tar.gz
,example[._-]v?(\d+(?:\.\d+)+)\.t
will continue matching if the upstream file name format changes toexample_1.2.3.tar.gz
orexample.1.2.3.tar.gz
. -
Use
\.t
in place of\.tgz
,\.tar\.gz
, etc. There are a variety of different file extensions for tarballs (e.g..tar.bz2
,tbz2
,.tar.gz
,.tgz
,.tar.xz
,.txz
, etc.) and the upstream source may switch from one compression format to another over time.\.t
avoids this issue by matching current and future formats starting witht
. Outside of tarballs, we use the full file extension in the regex like\.zip
,\.jar
, etc.
Example livecheck
blocks
The following examples cover a number of patterns that you may encounter. These are intended to be representative samples and can be easily adapted.
When in doubt, start with one of these examples instead of copy-pasting a livecheck
block from a random formula/cask.
File names
When matching the version from a file name on an HTML page, we often restrict matching to href
attributes. href=.*?
will match the opening delimiter ("
, '
) as well as any part of the URL before the file name.
livecheck do
url "https://www.example.com/downloads/"
regex(/href=.*?example[._-]v?(\d+(?:\.\d+)+)\.t/i)
end
We sometimes make this more explicit to exclude unwanted matches. URLs with a preceding path can use href=.*?/
and others can use href=["']?
. For example, this is necessary when the page also contains unwanted files with a longer prefix (another-example-1.2.tar.gz
).
Version directories
When checking a directory listing page, sometimes files are separated into version directories (e.g. 1.2.3/
). In this case, we must identify versions from the directory names.
livecheck do
url "https://www.example.com/releases/example/"
regex(%r{href=["']?v?(\d+(?:\.\d+)+)/?["' >]}i)
end
Git tags
When the stable
URL uses the Git
strategy, the following example will only match tags like 1.2
/v1.2
, etc.
livecheck do
url :stable
regex(/^v?(\d+(?:\.\d+)+)$/i)
end
If tags include the software name as a prefix (e.g. example-1.2.3
), it's easy to modify the regex accordingly: /^example[._-]v?(\d+(?:\.\d+)+)$/i
Referenced formula/cask
A formula/cask can use the same check as another by using formula
or cask
.
livecheck do
formula "another-formula"
end
The referenced formula/cask should be in the same tap, as a reference to a formula/cask from another tap will generate an error if the user doesn't already have it tapped.
strategy
blocks
If the upstream version format needs to be manipulated to match the formula/cask format, a strategy
block can be used instead of a regex
.
PageMatch
strategy
block
Here is a basic example, extracting a simple version from a page:
livecheck do
url "https://example.org/my-app/download"
regex(%r{href=.*?/MyApp-(\d+(?:\.\d+)*)\.zip}i)
strategy :page_match
end
More complex versions can be handled by specifying a block.
livecheck do
url "https://example.org/my-app/download"
regex(%r{href=.*?/(\d+)/MyApp-(\d+(?:\.\d+)*)\.zip}i)
strategy :page_match do |page, regex|
match = page.match(regex)
next if match.blank?
"#{match[2]},#{match[1]}"
end
end
In the example below, we're scanning the contents of the homepage for a date format like 2020-01-01
and converting it into 20200101
.
livecheck do
url :homepage
strategy :page_match do |page|
page.scan(/href=.*?example[._-]v?(\d{4}-\d{2}-\d{2})\.t/i)
.map { |match| match&.first&.gsub(/\D/, "") }
end
end
The PageMatch
strategy
block style seen here also applies to any site-specific strategy that uses PageMatch
internally.
HeaderMatch
strategy
block
A strategy
block for HeaderMatch
will try to parse a version from the filename (in the Content-Disposition
header) and the final URL (in the Location
header). If that doesn't work, a regex
can be specified.
livecheck do
url "https://example.org/my-app/download/latest"
regex(/MyApp-(\d+(?:\.\d+)*)\.zip/i)
strategy :header_match
end
If the version depends on multiple header fields, a block can be specified.
livecheck do
url "https://example.org/my-app/download/latest"
strategy :header_match do |headers|
v = headers["content-disposition"][/MyApp-(\d+(?:\.\d+)*)\.zip/i, 1]
id = headers["location"][%r{/(\d+)/download$}i, 1]
next if v.blank? || id.blank?
"#{v},#{id}"
end
end
Git
strategy
block
A strategy
block for Git
is a bit different, as the block receives an array of tag strings instead of a page content string. Similar to the PageMatch
example, this is converting tags with a date format like 2020-01-01
into 20200101
.
livecheck do
url :stable
strategy :git do |tags|
tags.filter_map { |tag| tag[/^(\d{4}-\d{2}-\d{2})$/i, 1]&.gsub(/\D/, "") }
end
end
GithubLatest
strategy
block
A strategy
block for GithubLatest
receives the parsed JSON data from the GitHub API for a repository's "latest" release, along with a regex. When a regex is not provided in a livecheck
block, the strategy's default regex is passed into the strategy
block instead.
By default, the strategy matches version text in the release's tag or title but a strategy
block can be used to check any of the fields in the release JSON. The logic in the following strategy
block is similar to the default behavior but only checks the release tag instead, for the sake of demonstration:
livecheck do
url :stable
regex(/^example[._-]v?(\d+(?:\.\d+)+)$/i)
strategy :github_latest do |json, regex|
match = json["tag_name"]&.match(regex)
next if match.blank?
match[1]
end
end
You can find more information on the response JSON from this API endpoint in the related GitHub REST API documentation.
GithubReleases
strategy
block
A strategy
block for GithubReleases
receives the parsed JSON data from the GitHub API for a repository's most recent releases, along with a regex. When a regex is not provided in a livecheck
block, the strategy's default regex is passed into the strategy
block instead.
By default, the strategy matches version text in each release's tag or title but a strategy
block can be used to check any of the fields in the release JSON. The logic in the following strategy
block is similar to the default behavior but only checks the release tag instead, for the sake of demonstration:
livecheck do
url :stable
regex(/^example[._-]v?(\d+(?:\.\d+)+)$/i)
strategy :github_releases do |json, regex|
json.map do |release|
next if release["draft"] || release["prerelease"]
match = release["tag_name"]&.match(regex)
next if match.blank?
match[1]
end
end
end
You can find more information on the response JSON from this API endpoint in the related GitHub REST API documentation.
Crate
strategy
block
A strategy
block for Crate
receives parsed JSON data from the registry API's versions
endpoint and either the provided or default strategy regex. The strategy uses the following logic by default, so this strategy
block may be a good starting point for a modified approach:
livecheck do
url :stable
strategy :crate do |json, regex|
json["versions"]&.map do |version|
next if version["yanked"]
next unless (match = version["num"]&.match(regex))
match[1]
end
end
end
ElectronBuilder
strategy
block
A strategy
block for ElectronBuilder
fetches content at a URL and parses it as an electron-builder appcast in YAML format. It's used for casks of macOS applications built using the Electron framework.
livecheck do
url "https://example.org/my-app/latest-mac.yml"
strategy :electron_builder
end
If you need to modify the version, you can access the YAML hash in the strategy
block like so:
livecheck do
url "https://example.org/my-app/latest-mac.yml"
strategy :electron_builder do |yaml|
yaml["version"]&.gsub(/\D/, "")
end
end
Similarly, you can work with the files
array like this:
livecheck do
url "https://example.org/my-app/latest-mac.yml"
regex(/MyApp[._-]v?(\d+(?:\.\d+)+)-(\h+)\.dmg/i)
strategy :electron_builder do |yaml, regex|
yaml["files"]&.map do |file|
match = file["url"]&.match(regex)
next if match.blank?
"#{match[1]},#{match[2]}"
end
end
end
Json
strategy
block
A strategy
block for Json
receives parsed JSON data and, if provided, a regex. For example, if we have an object containing an array of objects with a version
string, we can select only the members that match the regex and isolate the relevant version text as follows:
livecheck do
url "https://www.example.com/example.json"
regex(/^v?(\d+(?:\.\d+)+)$/i)
strategy :json do |json, regex|
json["versions"].select { |item| item["version"]&.match?(regex) }
.map { |item| item["version"][regex, 1] }
end
end
Sparkle
strategy
block
A strategy
block for Sparkle
receives an item
which has methods for the version
, short_version
, nice_version
, url
, channel
and title
. It expects a URL for an XML feed providing release information to a macOS application that self-updates using the Sparkle framework. This URL can be found within the app bundle as the SUFeedURL
property in Contents/Info.plist
or by using the find-appcast
command. Run it with:
brew find-appcast '/path/to/application.app'
The default pattern for the Sparkle
strategy is to generate "#{item.short_version},#{item.version}"
from sparkle:shortVersionString
and sparkle:version
if both are set. In the example below, the url
also includes a download ID which is needed:
livecheck do
url "https://www.example.com/example.xml"
strategy :sparkle do |item|
"#{item.short_version},#{item.version}:#{item.url[%r{/(\d+)/[^/]+\.zip}i, 1]}"
end
end
To use only one, specify &:version
, &:short_version
or &:nice_version
:
livecheck do
url "https://www.example.com/example.xml"
strategy :sparkle, &:short_version
end
Xml
strategy
block
A strategy
block for Xml
receives an REXML::Document
object and, if provided, a regex. For example, if the XML contains a versions
element with nested version
elements and their inner text contains the version string, we could extract it using a regex as follows:
livecheck do
url "https://www.example.com/example.xml"
regex(/v?(\d+(?:\.\d+)+)/i)
strategy :xml do |xml, regex|
xml.get_elements("versions//version").map { |item| item.text[regex, 1] }
end
end
For more information on how to work with an REXML::Document
object, please refer to the REXML::Document
and REXML::Element
documentation.
Yaml
strategy
block
A strategy
block for Yaml
receives parsed YAML data and, if provided, a regex. Borrowing the Json
example, if we have an object containing an array of objects with a version
string, we can select only the members that match the regex and isolate the relevant version text as follows:
livecheck do
url "https://www.example.com/example.yaml"
regex(/^v?(\d+(?:\.\d+)+)$/i)
strategy :yaml do |yaml, regex|
yaml["versions"].select { |item| item["version"]&.match?(regex) }
.map { |item| item["version"][regex, 1] }
end
end
ExtractPlist
strategy
block
If no means are available online for checking which version of a macOS package is current, as a last resort the :extract_plist
strategy will have brew livecheck
download the artifact and retrieve its version string from contained .plist
files.
livecheck do
url :url
strategy :extract_plist
end
A strategy
block for ExtractPlist
receives a hash containing keys for each found bundle identifier and item
s with methods for each version
and short_version
.
livecheck do
url :url
strategy :extract_plist do |items|
items["com.example.MyApp"].short_version
end
end
skip
Livecheck automatically skips some formulae/casks for a number of reasons (deprecated, disabled, etc.). However, on rare occasions we need to use a livecheck
block to do a manual skip. The skip
method takes a string containing a very brief reason for skipping.
livecheck do
skip "No version information available"
end