Skip to content

Instantly share code, notes, and snippets.

@zenofile
Last active March 13, 2024 07:30
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save zenofile/d2acef1a0423e5081e74162cd5a0ae2d to your computer and use it in GitHub Desktop.
Save zenofile/d2acef1a0423e5081e74162cd5a0ae2d to your computer and use it in GitHub Desktop.
Building a static tmux binary with musl
#!/usr/bin/env bash
# vi: set ft=sh ts=4 sw=0 sts=-1 sr noet nosi tw=0 fdm=manual:
set -o errexit
declare -A version
version[musl]=1.2.4
version[libevent]=2.1.12
version[ncurses]=6.4
version[tmux]=3.3a
targetdir=$1
if [[ ${targetdir} == "" ]]; then
targetdir=${PWD}/out
fi
mkdir -p "${targetdir}"
jobs=$(($(nproc) + 1))
CC=("${targetdir}/bin/musl-gcc" -static)
_CFLAGS=(-Os -ffunction-sections -fdata-sections)
if "${REALGCC:-gcc}" -v 2>&1 | grep -q -- --enable-default-pie; then
_CFLAGS+=(-no-pie)
fi
_LDFLAGS=("-Wl,--gc-sections" -flto)
_musl() {
if [[ ! -e musl-${version[musl]}.tar.gz ]]; then
curl -LO "https://www.musl-libc.org/releases/musl-${version[musl]}.tar.gz"
fi
tar zxf "musl-${version[musl]}.tar.gz" --skip-old-files
pushd .
cd "musl-${version[musl]}"
CFLAGS="${_CFLAGS[*]}" LDFLAGS="${_LDFLAGS[*]}" ./configure --prefix="${targetdir}" --disable-shared
make -j $jobs
make install
make clean
popd
}
_libevent() {
if [[ ! -e libevent-${version[libevent]}-stable.tar.gz ]]; then
curl -LO "https://github.com/libevent/libevent/releases/download/release-${version[libevent]}-stable/libevent-${version[libevent]}-stable.tar.gz"
fi
tar zxf "libevent-${version[libevent]}-stable.tar.gz" --skip-old-files
pushd .
cd "libevent-${version[libevent]}-stable"
_cflags=("${_CFLAGS[@]}" -flto)
CC="${CC[*]}" CFLAGS="${_cflags[*]}" LDFLAGS="${_LDFLAGS[*]}" ./configure --prefix="${targetdir}" --disable-shared --disable-openssl
make -j $jobs
make install
make clean
popd
}
_ncurses() {
if [[ ! -e ncurses-${version[ncurses]}.tar.gz ]]; then
curl -LO "https://ftp.gnu.org/pub/gnu/ncurses/ncurses-${version[ncurses]}.tar.gz"
fi
tar zxf "ncurses-${version[ncurses]}.tar.gz" --skip-old-files
pushd .
cd "ncurses-${version[ncurses]}"
_cflags=("${_CFLAGS[@]}" -flto)
CC="${CC[*]}" CFLAGS="${_cflags[*]}" LDFLAGS="${_LDFLAGS[*]}" ./configure --prefix "$targetdir" \
--with-default-terminfo-dir=/usr/share/terminfo \
--with-terminfo-dirs="/etc/terminfo:/lib/terminfo:/usr/share/terminfo" \
--enable-pc-files \
--with-pkg-config-libdir="${targetdir}/lib/pkgconfig" \
--without-ada \
--without-debug \
--with-termlib \
--without-cxx \
--without-progs \
--without-manpages \
--disable-db-install \
--without-tests
make -j $jobs
make install
make clean
popd
}
_tmux() {
if [[ ! -e tmux-${version[tmux]}.tar.gz ]]; then
curl -LO "https://github.com/tmux/tmux/releases/download/${version[tmux]}/tmux-${version[tmux]}.tar.gz"
fi
tar zxf "tmux-${version[tmux]}.tar.gz" --skip-old-files
pushd .
cd "tmux-${version[tmux]}"
_cflags=("${_CFLAGS[@]}" -flto "-I${targetdir}/include/ncurses/")
CC="${CC[*]}" CFLAGS="${_cflags[*]}" LDFLAGS="${_LDFLAGS[*]}" PKG_CONFIG_PATH="${targetdir}/lib/pkgconfig" ./configure --enable-static --prefix="${targetdir}"
make -j $jobs
make install
make clean
popd
cp "${targetdir}/bin/tmux" .
strip --strip-all ./tmux
if command -v upx &>/dev/null; then
upx -k --best ./tmux
fi
}
rm -rf "${targetdir}/out"
if [[ ! -x "${targetdir}/bin/musl-gcc" ]]; then
_musl
fi
_libevent
_ncurses
_tmux
@meermanr
Copy link

meermanr commented Jun 3, 2021

Thanks for posting this! I had to modify line 86 to avoid an error from autoconf about not being able to find curses.h

diff --git a/build.sh b/build.sh
index 3ad04f0..13fcbfd 100644
--- a/build.sh
+++ b/build.sh
@@ -83,7 +83,7 @@ _tmux() {
 	tar zxf tmux-${TMUX_VERSION}.tar.gz --skip-old-files
 	pushd .
 	cd tmux-${TMUX_VERSION}
-	_cflags=( "${_CFLAGS[@]}" -flto )
+	_cflags=( "${_CFLAGS[@]}" -flto "-I${TARGETDIR}/include/ncurses/" )
 	CC="${CC[*]}" CFLAGS="${_cflags[*]}" LDFLAGS="${_LDFLAGS[*]}" PKG_CONFIG_PATH="${TARGETDIR}/lib/pkgconfig" ./configure --enable-static --prefix="${TARGETDIR}"
 	make -j $TASKS
 	make install

I also changed the TASK setting to detect the number of processors:

diff --git a/build.sh b/build.sh
index 3ad04f0..13fcbfd 100644
--- a/build.sh
+++ b/build.sh
@@ -13,7 +13,7 @@ if [[ "${TARGETDIR}" = "" ]]; then
 fi
 mkdir -p "${TARGETDIR}"

-TASKS=2
+TASKS=$(nproc)
 CC=( "${TARGETDIR}/bin/musl-gcc" -static )
 _CFLAGS=( -Os -ffunction-sections -fdata-sections -no-pie)
 _LDFLAGS=( "-Wl,--gc-sections" -flto )

@meermanr
Copy link

meermanr commented Jun 3, 2021

And for anyone interested, I wrote a Vagrantfile that runs this script in a clean Ubuntu 20.04 LTS install:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-20.04"

  config.vm.provision "Install compiler etc",
    type: "shell",
    inline: <<-SHELL
      apt-get update
      apt-get install -y build-essential
    SHELL

  config.vm.provision "Download script", 
    type: "shell",
    privileged: false,
    inline: <<-SHELL
      wget -qO build.sh https://gist.github.com/zenofile/d2acef1a0423e5081e74162cd5a0ae2d/raw/2c2e73a298c0d822773ceeca69c3cace8fce4c96/build.sh
      SHELL

  config.vm.provision "Patch script", 
    type: "shell",
    privileged: false,
    inline: <<-SHELL
      DATA="
        ZGlmZiAtLWdpdCBhL2J1aWxkLnNoIGIvYnVpbGQuc2gKaW5kZXggM2FkMDRmMC4uMTNmY2JmZCAx
        MDA2NDQKLS0tIGEvYnVpbGQuc2gKKysrIGIvYnVpbGQuc2gKQEAgLTEzLDcgKzEzLDcgQEAgaWYg
        W1sgIiR7VEFSR0VURElSfSIgPSAiIiBdXTsgdGhlbgogZmkKIG1rZGlyIC1wICIke1RBUkdFVERJ
        Un0iCiAKLVRBU0tTPTIKK1RBU0tTPSQobnByb2MpCiBDQz0oICIke1RBUkdFVERJUn0vYmluL211
        c2wtZ2NjIiAtc3RhdGljICkKIF9DRkxBR1M9KCAtT3MgLWZmdW5jdGlvbi1zZWN0aW9ucyAtZmRh
        dGEtc2VjdGlvbnMgLW5vLXBpZSkKIF9MREZMQUdTPSggIi1XbCwtLWdjLXNlY3Rpb25zIiAtZmx0
        byApCkBAIC04Myw3ICs4Myw3IEBAIF90bXV4KCkgewogCXRhciB6eGYgdG11eC0ke1RNVVhfVkVS
        U0lPTn0udGFyLmd6IC0tc2tpcC1vbGQtZmlsZXMKIAlwdXNoZCAuCiAJY2QgdG11eC0ke1RNVVhf
        VkVSU0lPTn0KLQlfY2ZsYWdzPSggIiR7X0NGTEFHU1tAXX0iIC1mbHRvICkKKwlfY2ZsYWdzPSgg
        IiR7X0NGTEFHU1tAXX0iIC1mbHRvICItSSR7VEFSR0VURElSfS9pbmNsdWRlL25jdXJzZXMvIiAp
        CiAJQ0M9IiR7Q0NbKl19IiBDRkxBR1M9IiR7X2NmbGFnc1sqXX0iIExERkxBR1M9IiR7X0xERkxB
        R1NbKl19IiBQS0dfQ09ORklHX1BBVEg9IiR7VEFSR0VURElSfS9saWIvcGtnY29uZmlnIiAuL2Nv
        bmZpZ3VyZSAtLWVuYWJsZS1zdGF0aWMgLS1wcmVmaXg9IiR7VEFSR0VURElSfSIKIAltYWtlIC1q
        ICRUQVNLUwogCW1ha2UgaW5zdGFsbAo=
        "
      base64 --decode --ignore-garbage <<<$DATA > build.diff
      patch -p1 < build.diff
    SHELL

  config.vm.provision "Build TMUX", 
    type: "shell",
    privileged: false,
    inline: <<-SHELL
      bash -x ./build.sh
    SHELL

  config.vm.provision "Copy out TMUX binary", 
    type: "shell",
    privileged: false,
    inline: <<-SHELL
      cp -v tmux /vagrant/
    SHELL
end

@zenofile
Copy link
Author

zenofile commented Jun 3, 2021

That's awesome, thanks!

I was building it in an Archlinux container using podman and didn't test any other build environment.

podman run --rm -it --hostname archns --userns=host --tmpfs /tmp:exec,rw -v ./build.sh:/tmp/build/build.sh -v /tmp/build-tmux:/tmp/build --workdir=/tmp/build --security-opt label=disable gitlab.archlinux.org:5050/archlinux/archlinux-docker:base-devel-master /bin/bash /tmp/build/build.sh

For anyone that might be preferring podman or docker.

@asdffdsazqqq
Copy link

hello, thanks so much,

i am using a shared linux vm, no root access, spent days trying replace the very old tmux, lacked the skills.

wanted to let you know your script works with tmux 3.2a,
TMUX_VERSION=3.2a

really, thanks!

@ursetto
Copy link

ursetto commented Mar 25, 2022

Fantastic. In order to build on older systems, with a gcc that doesn't support -no-pie, add the following patch:

diff --git a/build.sh b/build.sh
index e7f6c33..720226e 100755
--- a/build.sh
+++ b/build.sh
@@ -15,7 +15,10 @@ mkdir -p "${TARGETDIR}"

 TASKS=2
 CC=( "${TARGETDIR}/bin/musl-gcc" -static )
-_CFLAGS=( -Os -ffunction-sections -fdata-sections -no-pie)
+_CFLAGS=( -Os -ffunction-sections -fdata-sections )
+if "${REALGCC:-gcc}" -v 2>&1 | grep -q -- --enable-default-pie; then
+    _CFLAGS+=( -no-pie )
+fi
 _LDFLAGS=( "-Wl,--gc-sections" -flto )

The -no-pie flag is illegal if gcc wasn't compiled with --enable-default-pie; in this case omitting it has the same effect.

With this change, it builds perfectly on CentOS 7. (For example in a centos:7 Dockerfile, use yum install -y gcc make autoconf and run build.sh.)

Adding @meermanr's patch above, it also builds in an ubuntu:20.04 container. In theory, it should build in a standard GitHub Actions runner.

@zenofile
Copy link
Author

I applied some of the suggested patches and bumped the versions. Also smoke tested with gcc-11 on aarch64 and x86_64.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment