Execute a command for the current directory on multiple architectures and operating systems.
Hydra Run, or hydrun, is a thin (<200 SLOC) layer atop Docker buildx and qemu-user-static. It allows one to easily execute a command on different processor architectures and operating systems than the host.
It can, for example, be used for ...
- Cross-compilation that "just works", without having to set up a cross-compiler (at the cost of longer build times)
- Multi-architecture testing
- Building arm64 binaries on GitHub actions, which doesn't support arm64 runners or Linux distros other than Ubuntu
- Quickly getting an interactive arm64 shell for the current directory on an amd64 host or the other way round
- Running binaries built against glibc on an Alpine Linux host
- Making CI release builds locally reproducable and testable, without having to
git push
and wait
Static binaries are also available on GitHub releases.
On Linux, you can install them like so:
$ curl -L -o /tmp/hydrun "https://github.com/pojntfx/hydrun/releases/latest/download/hydrun.linux-$(uname -m)"
$ sudo install /tmp/hydrun /usr/local/bin
On macOS, you can use the following:
$ curl -L -o /tmp/hydrun "https://github.com/pojntfx/hydrun/releases/latest/download/hydrun.darwin-$(uname -m)"
$ sudo install /tmp/hydrun /usr/local/bin
On Windows, the following should work (using PowerShell as administrator):
PS> Invoke-WebRequest https://github.com/pojntfx/hydrun/releases/latest/download/hydrun.windows-x86_64.exe -OutFile \Windows\System32\hydrun.exe
You can find binaries for more operating systems and architectures on GitHub releases.
Before continuing, please ensure that you have both Docker buildx and qemu-user-static installed on the host.
As described in the Reference, you can get an interactive shell by using the -i
flag. The -a
parameter corresponds to an architecture such as amd64
, arm64
or ppc64le
; the -o
flag corresponds to a Docker image such as debian
, alpine
or fedora
. To for example run arm64 Debian on an amd64 host, you can do the following:
$ uname -a
Linux dev-tmp 4.19.0-10-cloud-amd64 #1 SMP Debian 4.19.132-1 (2020-07-24) x86_64 GNU/Linux
$ hydrun -a arm64 -o debian -i "bash"
root@81647bd6aa02:/data# uname -a
Linux 81647bd6aa02 4.19.0-10-cloud-amd64 #1 SMP Debian 4.19.132-1 (2020-07-24) aarch64 GNU/Linux
root@81647bd6aa02:/data# ldd $(which ls)
libselinux.so.1 => /lib/aarch64-linux-gnu/libselinux.so.1 (0x0000005501868000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x000000550189e000)
/lib/ld-linux-aarch64.so.1 (0x0000005500000000)
libpcre.so.3 => /lib/aarch64-linux-gnu/libpcre.so.3 (0x0000005501a10000)
libdl.so.2 => /lib/aarch64-linux-gnu/libdl.so.2 (0x0000005501a83000)
libpthread.so.0 => /lib/aarch64-linux-gnu/libpthread.so.0 (0x0000005501a97000)
It is very easy to use hydrun to get binaries for many platforms, without having to set up cross-compilation. Consider the following C hello world program:
/* main.c */
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
Using hydrun, we can now compile it for multiple architectures:
$ ls
ls
main.c
$ hydrun -a amd64,arm64 -o gcc 'gcc -static -o hello-world.linux-$(uname -m) main.c'
$ ls
hello-world.linux-aarch64 hello-world.linux-x86_64 main.c
$ file *
hello-world.linux-aarch64: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.7.0, with debug_info, not stripped
hello-world.linux-x86_64: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, with debug_info, not stripped
main.c: C source, ASCII text
It is also possible to run/test the compiled binaries with it:
$ ls
hello-world.linux-aarch64 hello-world.linux-x86_64 main.c
$ hydrun -i -a arm64 "./hello-world.linux-aarch64"
Hello, world!
$ hydrun -i -a amd64 "./hello-world.linux-x86_64"
Hello, world!
When building larger projects or requiring dependencies, it is recommended to put the commands used into a shell script, conventionally named the Hydrunfile
:
$ cat <<EOT>Hydrunfile
#!/bin/bash
apt update
apt install -y build-essentials
gcc -static -o hello-world.linux-$(uname -m) main.c
EOT
$ chmod +x ./Hydrunfile
You can now easily build like so:
$ hydrun -a amd64,arm64 "./Hydrunfile"
$ ls
hello-world.linux-aarch64 hello-world.linux-x86_64 Hydrunfile main.c
Most of the time you'll probably want to use the Hydrunfile
to install toolchains/dependencies and then call your Makefile
, the Go compiler, cargo
etc.
For an example, check out pojntfx/panrpc.
It is also possible to use hydrun to build multi-architecture binaries in a CI/CD system such as GitHub actions. Continuing with the C example from above, we could automatically build and release amd64 and arm64 binaries to GitHub releases using GitHub actions with the following workflow:
name: hydrun CI
on:
push:
pull_request:
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Set up hydrun
run: |
curl -L -o /tmp/hydrun https://github.com/pojntfx/hydrun/releases/latest/download/hydrun.linux-$(uname -m)
sudo install /tmp/hydrun /usr/local/bin
- name: Build with hydrun
run: hydrun -a amd64,arm64 ./Hydrunfile
- name: Publish to GitHub releases
if: ${{ github.ref == 'refs/heads/main' }}
uses: marvinpinto/action-automatic-releases@latest
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: "latest"
prerelease: false
files: |
*.linux*
For an example, check out pojntfx/panrpc.
$ hydrun --help
Execute a command for the current directory on multiple architectures and operating systems.
See https://github.com/pojntfx/hydrun for more information.
Usage: hydrun [OPTION...] "<COMMAND...>"
-a, --arch string Comma-separated list of architectures to run on (default "amd64")
-c, --context string Directory to use in the container (default is the current working directory)
-e, --extra-args string Extra arguments to pass to the Docker command
-i, --it Attach stdin and setup a TTY
-j, --jobs int Maximum amount of parallel jobs (default 1)
-m, --mount Enable mounting the directory specified with the context flag (default true)
-o, --os string Comma-separated list of operating systems (Docker images) to run on (default "debian")
-p, --pull Always pull the specified tags of the operating systems (Docker images)
-q, --quiet Disable logging executed commands
-r, --readyOnly Mount the directory specified as read-only
To contribute, please use the GitHub flow and follow our Code of Conduct.
To build hydrun locally, run:
$ git clone https://github.com/pojntfx/hydrun.git
$ cd hydrun
$ go run main.go --help
Have any questions or need help? Chat with us on Matrix!
If you want to quickly cross-compile your Go app, check out bagop!
hydrun (c) 2024 Felicitas Pojtinger and contributors
SPDX-License-Identifier: AGPL-3.0