How to Package Rust Applications
Packaging applications can be extremely specific, and messing up small details can have huge consequences for your users. In this article, I'll be going over how to package your Rust applications for crates.io, the Archlinux User Repository (AUR), and Homebrew. The principles I discuss in this article can be applied to other packaging formats like an APT Personal Package Archive (PPA), Fedora's Copr Repos, or even a Nixpkg. Additionally, the article's contents can also be applied to applications written in other languages; packaging your application is not limited to stuff written in Rust. The only thing that would be different is some of the compile-time dependencies and some of the operations you perform in the respective build functions.
For the purposes of this article, I'll be using my application didyoumean as an example; check it out!
Crates.io
Publishing your package to crates.io is the easiest and most accessible way to release your application to the public. It will allow your application to be installed by anyone running Linux, macOS, Windows, or any other operating system that cargo, the Rust package manager, supports. The caveat to this is that your user will have to have the entire Rust toolchain installed, which is often non-trivial for a non-tech-savvy user. Another caveat is that, unlike the other packaging standards here, crates.io will only accept applications written in Rust. Without further ado, let's get into it.
Before you write any build files, you need to link your cargo CLI utility to your crates.io account. If you don't already have a crates.io account, go ahead and create one. Then, grab the token from your account settings and link your cargo utility with your crates.io account with cargo login <token>.
You then need to add yourself as an owner by running cargo owner --add <username>.
The information that crates.io reads comes from your Cargo.toml configuration file. Some common entries include: name, version, authors, license, description, readme, homepage, repository, keywords, and categories.
[package]
# Your package name. This is what people will type in to install your application.
name = "didyoumean"
# The name of any authors in a comma-separated list.
authors = ["Hisbaan Noorani"]
# The package version. This needs to be incremented every time you publish.
version = "1.1.2"
# The Rust toolchain version your application targets.
edition = "2021"
# The license that your application is released under.
license = "GPL-3.0"
# A description of your application.
description = "A CLI spelling corrector"
# A link to the documentation of your application.
documentation = "https://docs.rs/packages/didyoumean"
# A link to the homepage for your application. This can be something like
# your website or your GitHub readme.
homepage = "https://github.com/hisbaan/didyoumean"
# A relative path to your README in the files your upload.
readme = "docs/README.md"
# A list of keywords associated with your app. These have a maximum length
# of x characters, and you can only have 5 of them.
keywords = ["levenshtein", "damerau", "edit-distance", "spelling"]
# A list of categories that apply to your app. These must come from the
# list [here](https://crates.io/category_slugs)
categories = ["command-line-utilities"]
There are other optional variables you can define listed here.
You can then run cargo package --list to list all the files that will be published. If you're using git and there is something that you do not want to be included, then you can simply add this file to your .gitignore file or equivalent for other version control systems. You can also set up a variable if you're not using a version control system, which is described in the additional variables documentation I've linked above.
After this, I recommend you run cargo clippy to get some suggestions for common mistakes in your code. Then, run cargo fmt to automatically format your code according to the Rust formatting guidelines.
Then, you can run cargo publish to try and publish your application to crates.io. This will perform a test build of your application and ensure that everything in your Cargo.toml is valid. If everything is a-okay, cargo will publish your application to crates.io, and it will be accessible at https://crates.io/packages/<package name>.
Congrats! You've packaged your application for crates.io. A user can then install your package by running cargo install <package name>.
Archlinux User Repository (AUR)
The AUR is an excellent repository for Archlinux and Archlinux-based Linux distributions. It has a smaller compatibility window than something like crates.io; however, the users are often more dedicated. It also supports binary packages meaning the user won't have to have your chosen language's build tools installed to install your application. I'll cover the three main kinds of packages in this section. There are stable packages, git packages, and binary packages.
Before publishing anything to the AUR, you must first create an account and add your public ssh key to your account.
If there is anything that you want to know more about PKGBUILD files or the process of publishing to the AUR, check out this article on the Arch Wiki.
The following three sections cover why you would want to publish each kind of package and how to write the PKGBUILD file for each. There are common steps that will be covered after the three sections.
Git packages
Git packages are intended to build the application from your latest git revision. This makes them inherently unstable, so these are not the preferred installation method for most users. They are, however, the easiest kind of AUR package to maintain as you can effectively 'set it and forget it.' They are given the suffix -git to differentiate them from stable and binary packages.
Clone the repository at ssh://aur@aur.archlinux.org/<package name>-git. You can then create a PKGBUILD file. If you are on an Arch-based distribution, you can run cp /usr/share/pacman/PKGBUILD-vcs.proto PKGBUILD to get a simple template. Then, perform the following steps.
-
Open the file and delete anything related to any version control system other than
git. -
Remove the comments at the top of the file, except for the
# Maintainer...comment. Change this comment to your information. -
Change the
pkgnamevariable to your application name followed by the-gitsuffix. -
Change the
pkgvervariable to your package version. -
Change the
pkgdescvariable to describe what your application does. This should ideally be just one sentence. -
Add compatible architectures to a space-separated list of strings. This list should only contain valid architecture strings. An example of this variable could be:
arch=('i686' 'pentium4' 'x86_64' 'arm' 'armv7h' 'armv6h' 'aarch64') -
Change the
urlvariable to thehttpsURL of yourgitremote. An example of this would behttps://github.com/hisbaan/didyoumean. -
Add your license(s) to the
licensevariable. For the GPLv3.0 license, useGPL3. -
Add your application's runtime dependencies to the
dependenciesvariable. Most Rust applications built against GNU utils will depend ongcc-libs. -
Add your application's build dependencies to the
makedependsvariable. This will definitely includegitandcargobut may include more. -
If needed, add the
optdependsvariable. This is used to define optional dependencies for certain features. It is structured as follows:optdepends=('libxcb: X11 clipboard support' 'wl-clipboard: Wayland clipboard support') -
Replace
VCSin theprovidesandconflictsvariables withgit. -
If you are publishing a fork of another application, add that application's package to the
replacesvariable, so your package takes precedent over the package you are replacing. -
backupis for preserving user-made changes to a package. It is primarily intended for any configuration files in/etc. For example,/etc/pacman.conf. You can find more information about it here. -
Unless you need them, remove the
optionsandinstallvariables. -
Change the
sourcevariable togit+<your git url>. Since we defined theurlvariable above, we could also simply define this asgit+${url}. -
Remove the
noextractandmd5sumsvariables. -
Add the
sha256sumsvariable with the valuesha256sums=('SKIP'). We only skip this check since this is agitpackage, and we do not need to verify the authenticity of these files because ofgit's mechanisms. -
In the
pkgverfunction, replace the code present with the following if you have annotated tags as tied to your releases,pkgver() { cd "$srcdir/${pkgname%-VCS}" printf "%s" "$(git describe --long | sed 's/\([^-]*-\)g/r\1/;s/-/./g')" }Or the following if you do not
pkgver() { cd "$srcdir/${pkgname%-VCS}" printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" }As the comment in the initial function states, these are not absolute commands and need to be adapted to your git repository based on what information is available. The purpose of this function is to update the package version during installation to detect if the version you have installed is newer or older than the version present on the AUR.
-
Replace the contents of the
preparefunction withprepare() { export RUSTUP_TOOLCHAIN=stable export CARGO_TARGET_DIR=target cd "$srcdir/${pkgname%-git}" cargo fetch --locked --target "$CARCH-unknown-linux-gnu" }This will fetch the dependencies required for the rest of the installation to be offline. This is good practice but is not strictly needed. If you do not want to do this, you will have to modify the
cargo buildcommand in thebuildfunction in the next step. -
Replace the contents of the
buildfunction withbuild() { export RUSTUP_TOOLCHAIN=stable export CARGO_TARGET_DIR=target cd "$srcdir/${pkgname%-git}" cargo build --release --frozen strip target/release/${pkgname} # or the path to the binary # if it differs from your package name. }This will build the release build of your application and then reduce the binary side by removing unnecessary build information from the produced binary.
-
If you would like to, replace the contents of the
checkfunction to run your tests.check() { cd "$srcdir/${pkgname%-git}" export RUSTUP_TOOLCHAIN=stable export CARGO_TARGET_DIR=target cargo test --release --frozen }I do not do this in my packages as I have Github's CI/CD pipeline do the testing for me. The benefit of not including this is significantly reduced build times. Since Rust has a relatively slow build process, I don't include this in my packages.
-
Replace the contents of the
packagefunction withpackage() { cd "$srcdir/${pkgname%-git}" # replace ${pkgname} with the name of your binary if it differs from the package name. install -Dm755 target/release/${pkgname} -t "${pkgdir}/usr/bin/" # you do not need to install the license if it is present under `/usr/share/licenses/common/`. install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname%-git}/" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname%-git}/" }This will install the built binary to the system's binary directory and install the
LICENSEandREADME.mdfiles to their respective locations with the correct permissions.
And voila, you have finished writing the PKGBUILD file for your -git package. The following steps are testing it to ensure that you're not bricking people's machines with some bad commands.
For reference, my PKGBUILD for didyoumean-git is as follows:
# Maintainer: Hisbaan Noorani <hisbaan@gmail.com>
pkgname=didyoumean-git
pkgver=1.1.2.r0.c2c4c10
pkgrel=1
pkgdesc="A CLI spelling corrector"
arch=('i686' 'pentium4' 'x86_64' 'arm' 'armv7h' 'armv6h' 'aarch64')
url="https://github.com/hisbaan/didyoumean"
license=('GPL3')
depends=('gcc-libs')
makedepends=('git' 'cargo' 'binutils')
optdepends=('libxcb: X11 clipboard support'
'wl-clipboard: Wayland clipboard support')
provides=("${pkgname%-git}")
conflicts=("${pkgname%-git}")
source=('git+https://github.com/hisbaan/didyoumean')
sha256sums=('SKIP')
pkgver() {
cd "$srcdir/${pkgname%-git}"
printf "%s" "$(git describe --long | sed 's/v//;s/\([^-]*-\)g/r\1/;s/-/./g')"
}
prepare() {
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cd "$srcdir/${pkgname%-git}"
cargo fetch --locked --target "$CARCH-unknown-linux-gnu"
}
build() {
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cd "$srcdir/${pkgname%-git}"
cargo build --release --frozen
strip target/release/dym
}
package() {
cd "$srcdir/${pkgname%-git}"
install -Dm755 target/release/dym -t "${pkgdir}/usr/bin/"
install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname%-git}/"
install -Dm644 docs/README.md -t "${pkgdir}/usr/share/doc/${pkgname%-git}/"
}
Binary Packages
Binary packages are pre-built packages that a user can simply download and install. The benefit is that there is no build process, meaning that the user does not need to have the whole language toolchain installed nor deal with the overhead that building a package requires in terms of time and CPU usage. The PKGBUILD file for binary packages is the easiest to write but also the most work to maintain (especially if you are packaging binaries for multiple architectures). They are given the suffix -bin to distinguish themselves from the stable and git packages for the same application.
Clone the repository at ssh://aur@aur.archlinux.org/<package name>-bin. You can then create a PKGBUILD file. If you are on an Arch-based distribution, you can run cp /usr/share/pacman/PKGBUILD.proto PKGBUILD to get a simple template. Then, perform the following steps.
-
Remove the comments at the top of the file, except for the
# Maintainer...comment. Change this comment to your information. -
Change the
pkgnamevariable to your application name followed by the-binsuffix. -
Change the
pkgvervariable to your package version. -
Change the
pkgrelvariable to indicate minor releases or bug fixes. -
You can remove the
epochvariable. This variable indicates changes even more minuscule thanpkgreldenotes. -
Change the
pkgdescvariable to describe what your application does. This should ideally be just one sentence. -
Add compatible architectures to a space-separated list of strings. This list should only contain valid architecture strings. An example of this variable could be:
arch=('i686' 'pentium4' 'x86_64' 'arm' 'armv7h' 'armv6h' 'aarch64')However, for binary packages, you will usually only include one or maybe two of these.
-
Change the
urlvariable to thehttpsvariable of yourgitremote. An example of this would behttps://github.com/hisbaan/didyoumean. -
Add your license(s) to the
licensevariable. For the GPLv3.0 license, useGPL3. -
Remove the
groupsvariable. -
Add your application's runtime dependencies to the
dependenciesvariable. Most Rust applications built against GNU utils will depend ongcc-libs. -
Remove the
makedependsandcheck dependsvariables as this is a binary package, so there is no build process involved. -
If needed, add the
optdependsvariable. This is used to define optional dependencies for certain features. It is structured as follows:optdepends=('libxcb: X11 clipboard support' 'wl-clipboard: Wayland clipboard support') -
Add your package name to the
providesandconflictslist variables. -
If you are publishing a fork of another application, add that application's package to the
replacesvariable, so your package takes precedent over the package you are replacing. -
backupis for preserving user-made changes to a package. It's primarily intended for any configuration files in/etc. For example,/etc/pacman.conf. You can find more information about it here. -
Unless you need them, remove the
options,install, andchangelogvariables. -
Change the
sourcevariable to a direct link to your latest tar archive. This can be a linked file in your GitHub releases or anywhere else that you store your releases. You can create this tar archive by copying the files you wish to archive to a temporary directory and then using thetarcommand. An example would be as follows (from the project root):mkdir temp cd temp cp ../target/release/<binary name> . cp ../README.md . tar -czf <binary name>-<version number>-<system architecture>.tar.gz <binary name> README.md -
Remove the
noextract,md5sums, andvalidpgpkeysvariables. -
Add the
sha256sumsvariable and calculate thesha256hash of your tar archive by running thesha256sumcommand on it, then place the hash into the list. For example,sha256sums=("4c51cd1fcd5160a967c46007742a71aae9cc85dcdcf0c06e82711755f65d413e") -
Remove all functions except for the
packagefunction. -
Replace the contents of the
packagefunction withpackage() { cd "$srcdir/" install -Dm755 <binary name> -t "${pkgdir}/usr/bin/" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname%-bin}/" }This will install the pre-built binary to the system's binary directory and install the
README.mdfile to the appropriate location.
And voila, you have finished writing the PKGBUILD file for your -bin package. The following steps are testing it to ensure that you're not bricking people's machines with some bad commands.
For reference, my PKGBUILD for didyoumean-bin is as follows:
# Maintainer: Hisbaan Noorani <hisbaan@gmail.com>
pkgname=didyoumean-bin
pkgver=1.1.2
pkgrel=1
epoch=
pkgdesc="A CLI spelling corrector"
arch=('x86_64')
url="https://github.com/hisbaan/didyoumean"
license=('GPL3')
depends=('gcc-libs')
optdepends=('libxcb: X11 clipboard support'
'wayland: Wayland clipboard support')
provides=('didyoumean')
conflicts=('didyoumean')
source=("https://github.com/hisbaan/didyoumean/releases/download/v${pkgver}/dym-${pkgver}-x86_64.tar.gz")
sha256sums=("4c51cd1fcd5160a967c46007742a71aae9cc85dcdcf0c06e82711755f65d413e")
package() {
cd "$srcdir/"
install -Dm755 dym -t "${pkgdir}/usr/bin/"
install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname%-bin}/"
}
Stable Packages
Stable packages are intended to build the application from the source of your latest stable release. These have the downsides of both git and binary packages in that, like git packages, they require the user to build the package, and like binary packages, they must be refreshed on every release leading to more maintenance. The benefit of these packages is that the user can get a stable experience while still being able to compile the package themselves for an architecture that you do not package a binary version of. The added benefit is that they can read over the source code for your application before installing it.
Clone the repository at ssh://aur@aur.archlinux.org/<package name>. You can then create a PKGBUILD file. If you are on an Arch-based distribution, you can run cp /usr/share/pacman/PKGBUILD.proto PKGBUILD to get a simple template. Then, perform the following steps.
-
Remove the comments at the top of the file, except for the
# Maintainer...comment. Change this comment to your information. -
Change the
pkgnamevariable to your application name followed by the-gitsuffix. -
Change the
pkgvervariable to your package version. -
Change the
pkgrelvariable to indicate minor releases or bug fixes. -
You can remove the
epochvariable. This variable indicates changes even more minuscule thanpkgreldenotes. -
Change the
pkgdescvariable to describe what your application does. This should ideally be just one sentence. -
Add compatible architectures to a space-separated list of strings. This list should only contain valid architecture strings. An example of this variable could be:
arch=('i686' 'pentium4' 'x86_64' 'arm' 'armv7h' 'armv6h' 'aarch64') -
Change the
urlvariable to thehttpsvariable of yourgitremote. An example of this would behttps://github.com/hisbaan/didyoumean. -
Add your license(s) to the
licensevariable. For the GPLv3.0 license, useGPL3. -
Remove the
groupsvariable. -
Add your application's runtime dependencies to the
dependenciesvariable. Most Rust applications built against GNU utils will depend ongcc-libs. -
Add your application's build dependencies to the
makedependsvariable. This will definitely includegitandcargobut may include more. -
If needed, add the
optdependsvariable. This is used to define optional dependencies for certain features. It is structured as follows:optdepends=('libxcb: X11 clipboard support' 'wl-clipboard: Wayland clipboard support') -
Remove the
providesandconfictsvariables. Since this package does not have the-gitor-binsuffix, and the package name is automatically added to these two variables, we do not have to put anything in them. -
If you are publishing a fork of another application, add that application's package to the
replacesvariable, so your package takes precedent over the package you are replacing. -
backupis for preserving user-made changes to a package. It's primarily intended for any configuration files in/etc. For example,/etc/pacman.conf. You can find more information about it here. -
Unless you need them, remove the
options,install, andchangelogvariables. -
Change the
sourcevariable to the source code of the release you are targetting. If you are using GitHub releases, this could be:source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz") -
Remove the
noextractandmd5sumsvariables. -
Add the
sha256sumsvariable and calculate thesha256hash of your tar archive by running thesha256sumcommand on it, then place the hash into the list. For example,sha256sums=("4c51cd1fcd5160a967c46007742a71aae9cc85dcdcf0c06e82711755f65d413e") -
Replace the contents of the
preparefunction withprepare() { export RUSTUP_TOOLCHAIN=stable export CARGO_TARGET_DIR=target cd "$srcdir/${pkgname%-git}" cargo fetch --locked --target "$CARCH-unknown-linux-gnu" }This will fetch the dependencies required for the rest of the installation to be offline. This is good practice but is not strictly needed. If you do not want to do this, you will have to modify the
cargo buildcommand in thebuildfunction in the next step. -
Replace the contents of the
buildfunction withbuild() { export RUSTUP_TOOLCHAIN=stable export CARGO_TARGET_DIR=target cd "$srcdir/${pkgname%-git}" cargo build --release --frozen strip target/release/${pkgname} # or the path to the binary # if it differs from your package name. }This will build the release build of your application and then reduce the binary size by removing unnecessary build information from the produced binary.
-
If you would like to, replace the contents of the
checkfunction to run your tests.check() { cd "$srcdir/${pkgname%-git}" export RUSTUP_TOOLCHAIN=stable export CARGO_TARGET_DIR=target cargo test --release --frozen }I do not do this in my packages as I have Github's CI/CD pipeline do the testing for me. The benefit of not including this is significantly reduced build times. Since Rust has a relatively slow build process, I don't include this in my packages.
-
Replace the contents of the
packagefunction withpackage() { cd "$srcdir/${pkgname%-git}" # replace ${pkgname} with the name of your binary if it differs from the package name. install -Dm755 target/release/${pkgname} -t "${pkgdir}/usr/bin/" # you do not need to install the license if it is present under `/usr/share/licenses/common/`. install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname%-git}/" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname%-git}/" }This will install the built binary to the system's binary directory and install the
LICENSEandREADME.mdfiles to their respective locations with the correct permissions.
And voila, you have finished writing the PKGBUILD file for your stable package. The following steps are testing it to ensure that you're not bricking people's machines with some bad commands.
For reference, orhun's PKGBUILD for didyoumean is as follows:
# Maintainer: orhun <orhunparmaksiz@gmail.com>
# https://github.com/orhun/pkgbuilds
pkgname=didyoumean
pkgver=1.1.2
pkgrel=1
pkgdesc="A CLI spelling corrector"
arch=('x86_64')
url="https://github.com/hisbaan/didyoumean"
license=('GPL3')
depends=('gcc-libs' 'libxcb' 'openssl')
makedepends=('cargo')
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
sha512sums=('1e6cce23bdbb70b4039e252058141a4dc7705f312cd1ed7dc7a8fd389e7f3a975527b2033ac2e3e89b2b1daeea46970b630c763d904e5aa299e643b229bbfbb9')
prepare() {
cd "$pkgname-$pkgver"
cargo fetch --locked --target "$CARCH-unknown-linux-gnu"
}
build() {
cd "$pkgname-$pkgver"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build --release --frozen
}
check() {
cd "$pkgname-$pkgver"
cargo test --frozen --lib
}
package() {
cd "$pkgname-$pkgver"
install -Dm 755 "target/release/dym" -t "$pkgdir/usr/bin"
install -Dm 644 docs/README.md -t "$pkgdir/usr/share/doc/$pkgname"
}
Testing and Publishing
Now for the common parts of all three types of AUR packages. We will test that the package is building correctly. Go ahead and run makepkg in the same directory as your PKGBUILD. This will build your package and produce a .tar file of the output directories. To ensure that your package build is installing files to the correct location, run tar -tf <package name>-<version number>-<system architecture>.pkg.tar, which should produce something similar to:
.BUILDINFO
.MTREE
.PKGINFO
usr/
usr/bin/
usr/bin/dym
usr/share/
usr/share/doc/
usr/share/doc/didyoumean/
usr/share/doc/didyoumean/README.md
usr/share/licenses/
usr/share/licenses/didyoumean/
usr/share/licenses/didyoumean/LICENSE
This command will show all the directories and files in the tar archive. Ensure that your binary is in /usr/bin/<binary name>, and your license and readme are in the respective place if need be.
Additionally, you can build the package on a chroot or fresh install to ensure that you have not missed any dependencies that you already had installed. I won't cover that in this article, but there are many guides online.
To get the information that the AUR uses to describe the package, the AUR uses a pre-generated .SRCINFO file. This is because it may be potentially unsafe to read from or run an untrusted PKGBUILD file on the AUR's servers. To generate this file, run makepkg --printsrcinfo > .SRCINFO.
And you're just about done. Delete any build artifacts from our previous makepkg runs and push the PKGBUILD and .SRCINFO files to the AUR's git server.
Congrats! You've published your first AUR package. Users will be able to install it through their AUR helper of choice. With paru, the command would be:
paru -S <package name>
Homebrew
Homebrew is a package manager primarily targetting macOS. It works on other operating systems too, but it's safe to assume that the majority (if not all) of your users wanting your package on Homebrew will be running on macOS. This is an excellent point to do a sanity check and test whether your app actually works on macOS. Now that we've gone through that let's get on to actually packaging it. In this article, I'll only be covering binary packaging in Homebrew.
First, you will need to build a binary. If you're on macOS, then simply run cargo build --release as you usually would; however, there are some steps required to build a macOS binary if you're on another platform. Personally, I would recommend finding an Apple machine and simply building on that. However, if that is not an option, check out this excellent article by James Waples about cross-compiling to macOS. Additionally, it's good to run strip on the binary to remove any unnecessary build information.
Now that you have a built binary go ahead and compress it as a tar.gz archive by running the command tar -czf <binary name>-<version number>-<system architecture>.tar.gz <binary name>. Then make this archive accessible through some means. I personally put the binary release in my GitHub releases.
Next, create a GitHub repository with the prefix homebrew-. For example, my Homebrew Formula repository is titled homebrew-tap. This allows users to add your repository using the brew tap command. So to add my homebrew-tap repository, the user would run.
brew tap hisbaan/tap
Notice that the initial homebrew- is omitted from the command. This is intentional and is how the brew CLI is designed.
Now we'll get onto the repository structure. It should look something like this. The main files you should pay attention to are the Formula directory and the didyuomean.rb file. The LICENSE and README.md files are optional (but recommended).
homebrew-tap
├── Formula
│ └── didyoumean.rb
├── LICENSE
└── README.md
You can have more than one Formula in a single Homebrew tap, hence the Formula directory. Now we will explore the contents of the didyoumean.rb file. This Ruby file contains the necessary information to install the pre-built binary that we tar'd earlier.
class Didyoumean < Formula
desc "A CLI spelling corrector"
homepage "https://github.com/hisbaan/didyoumean"
url "https://github.com/hisbaan/didyoumean/releases/download/v1.1.2/dym-1.1.2-x86_64-apple-darwin.tar.gz"
sha256 "06167f4bd847f86b22440e0e51e7ab2b0ff38efe1e914c0e2650fff0433f229f"
version "1.1.2"
def install
bin.install "dym"
end
end
- The
descvariable is a description of your package - The
homepagevariable links to the project's home page. This could be a website or just agitrepo. - The
urlis a direct download link to your tarball. - The
sha256variable is asha256hash of the tarball obtained by runningsha256sumon the tarball. - The
versionvariable is the version number of your package. - The
installfunction is the process that will install your binary. We will use the built-inbin.installcommand. The only argument to the command should be the name of the binary in the tarball.
Go ahead and push your changes, and your program will b installable as follows:
brew tap <username>/tap # or a different repository
brew install <package name>
And just like that, you've packaged your application for Homebrew!
Conclusion
This brings our journey through packaging to an end... for now! There are lots of things that you can take away from this article. You can apply the things you've learned here to more packaging standards (as I'm sure you've noticed, they're all relatively similar with a few minor tweaks here and there) and applications built in more programming languages. Good luck with your journey ahead, and if you end up releasing anything, shoot a message my way, I'd love to see it!