brew/docs/Brew-Livecheck.md

358 lines
17 KiB
Markdown
Raw Normal View History

# `brew livecheck`
2020-07-08 00:32:02 -04:00
The `brew livecheck` command finds the newest version of a formula or cask's software by checking upstream. Livecheck has [strategies](https://rubydoc.brew.sh/Homebrew/Livecheck/Strategy) to identify versions from various sources, such as Git repositories, websites, etc.
2020-07-08 00:32:02 -04:00
## Behavior
2020-07-08 00:32:02 -04:00
When livecheck isn't given instructions for how to check for upstream versions, it does the following by default:
2020-07-08 00:32:02 -04:00
1. For formulae: Collect the `stable`, `head`, and `homepage` URLs, in that order (resources simply use their `url`). For casks: Collect the `url` and `homepage` URLs, in that order.
1. Determine if any strategies apply to the first URL. If not, try the next URL.
1. If a strategy can be applied, use it to check for new versions.
1. Return the newest version (or an error if versions could not be found at any available URLs).
2020-07-08 00:32:02 -04:00
2021-01-14 17:49:07 +01:00
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.
2020-07-08 00:32:02 -04:00
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](https://rubydoc.brew.sh/Livecheck).
2020-07-08 00:32:02 -04:00
## Creating a check
2020-07-08 00:32:02 -04:00
2022-10-02 16:53:51 +02:00
1. **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.
2020-07-08 00:32:02 -04:00
1. **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.
2022-09-27 14:45:49 +02:00
1. **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](#regex-guidelines) section.
2020-07-08 00:32:02 -04:00
### General guidelines
2020-07-08 00:32:02 -04:00
* **Only use `strategy` when it's necessary**. For example, if livecheck is already using the `Git` strategy for a URL, it's not necessary to use `strategy :git`. However, if `Git` applies to a URL but we need to use `PageMatch`, it's necessary to specify `strategy :page_match`.
2020-07-08 00:32:02 -04:00
2023-09-05 00:58:57 -04:00
* **Only use the `GithubLatest` and `GithubReleases` strategies when they are necessary and correct**. GitHub rate-limits API requests, so we only use these strategies when `Git` 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 the `Git` strategy can't correctly identify the latest release version. `GithubReleases` should only be used if the upstream repository uses releases and both the `Git` and `GithubLatest` strategies aren't suitable.
2020-07-08 00:32:02 -04:00
### URL guidelines
2020-07-08 00:32:02 -04:00
* **A `url` is required in a `livecheck` 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 a `livecheck` block that only uses `skip`.
2020-07-08 00:32:02 -04:00
* **Check for versions in the same location as the stable archive, whenever possible**.
2020-07-08 00:32:02 -04:00
* **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 the `Git` strategy, which fetches all the tags in the repository.
2020-07-08 00:32:02 -04:00
### Regex guidelines
2020-07-08 00:32:02 -04:00
The `livecheck` block regex restricts matches to a subset of the fetched content and uses a capture group around the version text.
2020-07-08 00:32:02 -04:00
* **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.
2020-07-08 00:32:02 -04:00
* **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 like `1.2`, `1.2.3`, etc.) and we're using non-capturing groups elsewhere (e.g. `(?:-src)?`).
2020-07-08 00:32:02 -04:00
* **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.
2020-07-08 00:32:02 -04:00
2023-07-17 12:30:12 -07:00
* **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 `[^"' >]+?`.
2020-07-08 00:32:02 -04:00
* **Use `[._-]` in place of a period/underscore/hyphen between the software name and version in a file name**. For a file named `example-1.2.3.tar.gz`, `example[._-]v?(\d+(?:\.\d+)+)\.t` will continue matching if the upstream file name format changes to `example_1.2.3.tar.gz` or `example.1.2.3.tar.gz`.
2020-07-08 00:32:02 -04:00
* **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 with `t`. Outside of tarballs, we use the full file extension in the regex like `\.zip`, `\.jar`, etc.
2020-07-08 00:32:02 -04:00
## Example `livecheck` blocks
2020-07-08 00:32:02 -04:00
The following examples cover a number of patterns that you may encounter. These are intended to be representative samples and can be easily adapted.
2020-07-08 00:32:02 -04:00
When in doubt, start with one of these examples instead of copy-pasting a `livecheck` block from a random formula/cask.
2020-07-08 00:32:02 -04:00
### File names
2020-07-08 00:32:02 -04:00
2021-01-14 17:49:07 +01:00
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.
```ruby
2021-01-14 17:49:07 +01:00
livecheck do
url "https://www.example.com/downloads/"
regex(/href=.*?example[._-]v?(\d+(?:\.\d+)+)\.t/i)
end
```
2020-07-08 00:32:02 -04:00
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`).
2020-07-08 00:32:02 -04:00
### Version directories
2020-07-08 00:32:02 -04:00
2021-01-14 17:49:07 +01:00
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.
```ruby
2021-01-14 17:49:07 +01:00
livecheck do
url "https://www.example.com/releases/example/"
regex(%r{href=["']?v?(\d+(?:\.\d+)+)/?["' >]}i)
end
```
2020-07-08 00:32:02 -04:00
### Git tags
2020-07-08 00:32:02 -04:00
2021-01-14 17:49:07 +01:00
When the `stable` URL uses the `Git` strategy, the following example will only match tags like `1.2`/`v1.2`, etc.
```ruby
2021-01-14 17:49:07 +01:00
livecheck do
url :stable
regex(/^v?(\d+(?:\.\d+)+)$/i)
end
```
2020-07-08 00:32:02 -04:00
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`
2020-07-08 00:32:02 -04:00
### Referenced formula/cask
A formula/cask can use the same check as another by using `formula` or `cask`.
```ruby
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.
2021-01-14 17:49:07 +01:00
### `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:
```ruby
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.
```ruby
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`.
```ruby
2021-01-14 17:49:07 +01:00
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
2021-01-14 17:49:07 +01:00
end
```
2020-07-08 00:32:02 -04:00
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.
```ruby
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.
```ruby
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
```
2020-07-08 00:32:02 -04:00
2021-01-14 17:49:07 +01:00
#### `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`.
2020-07-08 00:32:02 -04:00
```ruby
2021-01-14 17:49:07 +01:00
livecheck do
url :stable
strategy :git do |tags|
tags.map { |tag| tag[/^(\d{4}-\d{2}-\d{2})$/i, 1]&.gsub(/\D/, "") }.compact
end
2021-01-14 17:49:07 +01:00
end
```
2020-07-08 00:32:02 -04:00
2023-05-16 15:17:42 -04:00
#### `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:
```ruby
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](https://docs.github.com/en/rest/releases/releases?apiVersion=latest#get-the-latest-release).
#### `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:
```ruby
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)
2023-05-16 15:17:42 -04:00
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](https://docs.github.com/en/rest/releases/releases?apiVersion=latest#list-releases).
#### `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.
```ruby
livecheck do
url "https://example.org/my-app/latest-mac.yml"
strategy :electron_builder
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:
```ruby
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
```
2021-01-14 17:49:07 +01:00
#### `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`](https://github.com/Homebrew/homebrew-cask/blob/HEAD/developer/bin/find-appcast) script. Run it with:
2020-07-08 00:32:02 -04:00
```bash
"$(brew --repository homebrew/cask)/developer/bin/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:
2020-07-08 00:32:02 -04:00
```ruby
2021-01-14 17:49:07 +01:00
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
2021-01-14 17:49:07 +01:00
end
```
2020-07-08 00:32:02 -04:00
To use only one, specify `&:version`, `&:short_version` or `&:nice_version`:
```ruby
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:
```ruby
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`](https://ruby.github.io/rexml/REXML/Document.html) and [`REXML::Element`](https://ruby.github.io/rexml/REXML/Element.html) 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:
```ruby
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.
```ruby
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`.
```ruby
livecheck do
url :url
strategy :extract_plist do |items|
items["com.example.MyApp"].short_version
end
end
```
2021-01-14 17:49:07 +01:00
### `skip`
Livecheck automatically skips some formulae/casks for a number of reasons (deprecated, disabled, discontinued, 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.
2021-01-14 17:49:07 +01:00
```ruby
livecheck do
skip "No version information available"
end
```