I see a lot of this-setup or setup-that GitHub actions around. They all wrap
the @actions/tool-cache library to install some pre-compiled binary. If
tool-cache exists, why do we need all these separate actions? What happens if
I can’t find a -setup action for the tool I want to use? What if there are
multiple? Do I need to write yet-another curl | tar or gh release download?
Well, the reason for all these separate -setup actions is that
actions/tool-cache is fairly low-level. You need to know and provide a bunch
of tool-specific stuff to use it:
- The URL to the archive
- The format of the archive (i.e. gzip or zip)
- If the executables are in some sub-directory
These things vary wildly between tools, and that URL packs a lot of information
in it, like the pet names this project uses to encode OS and Architecture into
the path. So you can’t even assume something conventional if the tool uploads
binaries to a GitHub release. The result: hlint-setup hard-codes its things
and setup-pandoc hard-codes its things. And we all just keep doing this until
we die.
While I was considering writing a dms-setup-action to install the Dead Man’s
Snitch Field Agent, I wondered if we could do better. And so I created
pbrisbin/setup-tool-action instead. This action attempts to
support every possible -setup action’s use-case. In other words, if your
intent is to wrap @actions/tool-cache, you hopefully won’t need to now.
As a simple first example, here’s how I install pre-compiled weeder binaries
as part of freckle/weeder-action:
uses: pbrisbin/setup-tool-action@v1
with:
name: weeder
version: ${{ inputs.weeder-version }}
url: "https://github.com/freckle/weeder-action/releases/download/Binaries/{name}-{version}-{os}-{arch}.{ext}"
No separate weeder-setup action necessary.
As you might guess, the template-string for url allows you to define from the
outside how this particular tool names its pre-compiled binaries. Since I also
maintain weeder-action, I chose to release binaries using os and arch values
that match Node’s process.platform and process.arch values on GitHub
runners, which is what this template string is filled in with by default.
Of course, there’s no consensus in this world. As a slightly more complex case,
let’s fully replace haskell/actions/hlint-setup for a
hypothetical project that only runs it on ubuntu-latest:
with:
name: hlint
version: "3.5"
url: "https://github.com/ndmitchell/{name}/releases/download/v{version}/{name}-{version}-{arch}-{os}.{ext}"
subdir: "{name}-{version}"
arch: x86_64
This is not too bad, and we’ve cleanly dealt with three axis of variability:
- HLint has the rarer arch-then-os convention in the URL
- HLint uses
x86_64instead of thex64thatprocess.archgives - HLint packs its binaries in a sub-directory
The default os works on this runner since HLint releases use linux, which
matches process.platform. For non-Linux runners that’s not the case, so if
your project is running HLint on such a platform, you need to override os too.
But the values are themselves os-specific. To support such cases, all inputs
except name and version can be given at varying levels of
specificity:
<input>-<platform>-<arch>overrides,<input>-<arch>overrides,<input>-<platform>overrides,<input>
With that in mind, we can make a fully runner-agnostic version of this step:
with:
name: hlint
version: "3.5"
url: "https://github.com/ndmitchell/{name}/releases/download/v{version}/{name}-{version}-{arch}-{os}.{ext}"
subdir: "{name}-{version}"
os-darwin: osx
os-win32: windows
arch: x86_64
But surely the url won’t change by platform, right? Enter Pandoc:
with:
name: pandoc
version: 2.19.2
url: "https://github.com/jgm/{name}/releases/download/{version}/{name}-{version}-{os}-{arch}.{ext}"
url-darwin: "https://github.com/jgm/{name}/releases/download/{version}/{name}-{version}-macOS.zip"
subdir: "{name}-{version}/bin"
subdir-win32: "{name}-{version}"
os-win32: windows
arch: x86_64
arch-linux: amd64
(By the way, arch-linux is my favorite input.)
Yeah, this is a lot. But it’s still better than building and maintaining a whole-ass action right? It’s also worth noting that if you only ever run things on one runner, there’s no need to get so verbose. Just do what your runner needs:
# ubuntu
with:
name: pandoc
version: 2.19.2
url: "https://github.com/jgm/{name}/releases/download/{version}/{name}-{version}-{os}-amd64.{ext}"
subdir: "{name}-{version}/bin"
# macOS
with:
name: pandoc
version: 2.19.2
url: "https://github.com/jgm/{name}/releases/download/{version}/{name}-{version}-macOS.zip"
subdir: "{name}-{version}/bin"
# windows
with:
name: pandoc
version: 2.19.2
url: "https://github.com/jgm/{name}/releases/download/{version}/{name}-{version}-windows-x86_64.{ext}"
subdir: "{name}-{version}"
And what of the goal that spurned this yak? Can it install my Field Agent?
with:
name: dms
version: latest
url: "https://releases.deadmanssnitch.com/field-agent/{version}/{name}_{os}_{arch}.{ext}"
os-darwin: macos
os-win32: windows
arch: amd64
👌