Skip to content

Commit

Permalink
Support cross platform builds
Browse files Browse the repository at this point in the history
Add support for cross building the following platforms:

- `linux/386`
- `linux/amd64`
- `linux/arm/v6`
- `linux/arm/v7`
- `linux/arm64`
- `linux/ppc64le`
- `linux/s390x`

To build for a different platform other than the local native platform the
`qemu-user-static` package must be installed.

Example to build AMD64, ARM64 and ARMv7 platforms:
```
make PLATFORM="linux/amd64 linux/arm64 linux/arm/v7" all-images
```
  • Loading branch information
mmartinv committed Dec 15, 2022
1 parent 04f7bab commit e8c30e6
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 56 deletions.
64 changes: 53 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,73 @@
#

BUILDER ?= docker
PLATFORMS ?= linux/amd64
VERSION ?= latest
IMAGE ?= weechat
REGISTRY ?= docker.io
REGISTRY_PROJECT ?= weechat
REGISTRY_PROJECT_URL = $(REGISTRY)/$(REGISTRY_PROJECT)
REGISTRY_IMAGE = $(REGISTRY_PROJECT_URL)/$(IMAGE)

ALPINE_BASE_IMAGE = alpine:3.15
DEBIAN_BASE_IMAGE = debian:bullseye-slim

ifeq ($(strip $(PUSH)),true)
PUSH_ARG = "--push"
endif

.PHONY: all debian debian-slim alpine alpine-slim

all: debian

all-images: debian debian-slim alpine alpine-slim

debian:
./build.py -b "$(BUILDER)" -d "debian" "$(VERSION)"
debian: weechat-multiarch
./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "debian" "$(VERSION)"

debian-slim: weechat-multiarch
./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "debian" --slim "$(VERSION)"

debian-slim:
./build.py -b "$(BUILDER)" -d "debian" --slim "$(VERSION)"
alpine: weechat-multiarch
./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "alpine" "$(VERSION)"

alpine:
./build.py -b "$(BUILDER)" -d "alpine" "$(VERSION)"
alpine-slim: weechat-multiarch
./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "alpine" --slim "$(VERSION)"

alpine-slim:
./build.py -b "$(BUILDER)" -d "alpine" --slim "$(VERSION)"
weechat-multiarch:
if [ "$(BUILDER)" == "docker" ]; then \
docker buildx ls | grep weechat-multiarch || docker buildx create --name weechat-multiarch ; \
docker buildx use weechat-multiarch; \
fi

test-container:
"$(BUILDER)" run "$(IMAGE)" weechat --version
"$(BUILDER)" run "$(IMAGE)" weechat-headless --version
test-images:
for PLATFORM in $(PLATFORMS); do \
for TAG in $(VERSION)-{debian,alpine}{,-slim}; do \
REGISTRY_IMAGE_TAG=$(REGISTRY_IMAGE):$$TAG; \
echo "Testing $$REGISTRY_IMAGE_TAG for $$PLATFORM platform" ; \
$(BUILDER) pull --platform $$PLATFORM $$REGISTRY_IMAGE_TAG &>/dev/null; \
echo -n "weechat --version: "; \
$(BUILDER) run --rm --platform $$PLATFORM $$REGISTRY_IMAGE_TAG --version; \
echo -n "weechat-headless --version: "; \
$(BUILDER) run --rm --platform $$PLATFORM --entrypoint /usr/bin/weechat-headless $$REGISTRY_IMAGE_TAG --version ; \
$(BUILDER) rmi $$REGISTRY_IMAGE_TAG &>/dev/null; \
done; \
done

clean-images:
if [ "$(BUILDER)" == "podman" ]; then \
buildah rm --all; \
$(BUILDER) images --format="{{.Repository}}:{{.Tag}}" localhost/$(IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \
$(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(REGISTRY_PROJECT)/$(IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \
$(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(ALPINE_BASE_IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \
$(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(DEBIAN_BASE_IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \
$(BUILDER) image prune -f; \
fi
if [ "$(BUILDER)" == "docker" ]; then \
$(BUILDER) buildx rm weechat-multiarch; \
$(BUILDER) rmi moby/buildkit:buildx-stable-1
fi

lint: flake8 pylint mypy bandit

flake8:
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ The slim version includes all plugins except these ones:
- scripting languages: perl, python, ruby, lua, tcl, guile, php
- spell

## Supported platforms

It's possible to build for different linux platforms other than amd64,
the only prerequisite is to have `qemu-user-static` package installed

- `linux/386`
- `linux/amd64`
- `linux/arm/v6`
- `linux/arm/v7`
- `linux/arm64`
- `linux/ppc64le`
- `linux/s390x`


## Install from Docker Hub

You can install directly the latest version from the Docker Hub:
Expand Down Expand Up @@ -72,6 +86,13 @@ Build all images with latest stable version of WeeChat:
$ make all-images
```

Build all images with latest stable version of WeeChat for AMD64 and ARM64 platforms
and push them to the `docker.io/weechat` registry project:

```
$ make PLATFORMS="linux/amd64 linux/arm64" REGISTRY="docker.io" REGISTRY_PROJECT="weechat" PUSH=true all-images
```

Build an Alpine-based image with Podman, slim version, WeeChat 3.6:

```
Expand Down
28 changes: 15 additions & 13 deletions alpine/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,12 @@ ENV VERSION="${VERSION}"
ARG SLIM=""
ENV SLIM="${SLIM}"

ARG HOME="/home/user"
ARG HOME="/weechat"
ENV HOME="${HOME}"

ENV LANG="C.UTF-8"
ENV TERM="xterm-256color"

# create a user
RUN set -eux ; \
adduser -u 1001 -D -h "$HOME" user ; \
mkdir -p "$HOME/.weechat" ; \
mkdir -p "$HOME/.config/weechat" ; \
mkdir -p "$HOME/.local/share/weechat" ; \
mkdir -p "$HOME/.cache/weechat" ; \
chown -R user:user "$HOME"

# ==== build ====

FROM base as build
Expand Down Expand Up @@ -126,6 +117,8 @@ RUN set -eux ; \
libgcrypt \
ncurses-libs \
ncurses-terminfo \
shadow \
setpriv \
tzdata \
zlib \
zstd \
Expand All @@ -151,8 +144,17 @@ COPY --from=build /opt/weechat /opt/weechat
RUN ln -sf /opt/weechat/bin/weechat /usr/bin/weechat
RUN ln -sf /opt/weechat/bin/weechat-headless /usr/bin/weechat-headless

WORKDIR $HOME
# create a user
RUN set -eux ; \
adduser -u 1000 -D -h "$HOME" weechat; \
mkdir -p "$HOME/.weechat" ; \
mkdir -p "$HOME/.config/weechat" ; \
mkdir -p "$HOME/.local/share/weechat" ; \
mkdir -p "$HOME/.cache/weechat" ; \
chown -R weechat:weechat "$HOME"

USER user
ADD ../run.sh /

WORKDIR $HOME

CMD ["weechat"]
ENTRYPOINT [ "/run.sh" ]
138 changes: 119 additions & 19 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,54 @@

"""Build WeeChat container image."""

from typing import List, Sequence, Tuple
from typing import List, Sequence, Tuple, Dict

import argparse
import urllib.request
import subprocess # nosec

BUILDERS: Sequence[str] = (
"docker",
"podman",
)

DISTROS: Sequence[str] = (
"debian",
"alpine",
)

SUPPORTED_PLATFORMS: Dict[str, Dict[str, List[str]]] = {
"linux": {
"386": [],
"amd64": [],
"arm": ["v6", "v7"],
"arm64": [],
"ppc64le": [],
"s390x": [],
},
}


def generate_valid_platforms() -> list[str]:
"""Return the list of supported platforms."""
valid_platforms = []
for os_name, archs in SUPPORTED_PLATFORMS.items():
for arch, variants in archs.items():
if not variants:
valid_platforms.append(f"{os_name}/{arch}")
for variant in variants:
valid_platforms.append(f"{os_name}/{arch}/{variant}")
return valid_platforms


def get_parser() -> argparse.ArgumentParser:
"""Return the command line parser."""
parser = argparse.ArgumentParser(description="Build of WeeChat container")
parser.add_argument(
"-b",
"--builder",
default="docker",
choices=BUILDERS,
default=BUILDERS[0],
help=(
"program used to build the container image, "
"like docker (default) or podman"
Expand All @@ -47,7 +76,29 @@ def get_parser() -> argparse.ArgumentParser:
"--distro",
choices=DISTROS,
default=DISTROS[0],
help="base Linux distribution for the container",
help="base Linux distribution for the container, default %(default)s",
)
parser.add_argument(
"-p",
"--platforms",
default="linux/amd64",
choices=generate_valid_platforms(),
nargs="+",
help="build platforms",
)
parser.add_argument(
"--push",
action="store_true",
default=False,
help=(
"push the images to the registry, default: %(default)s"
),
)
parser.add_argument(
"-r",
"--registry-project",
default="docker.io/weechat",
help="registry project, default: %(default)s",
)
parser.add_argument(
"-n",
Expand Down Expand Up @@ -81,7 +132,7 @@ def get_version_tags(
:param version: x.y, x.y.z, "latest", "stable", "devel"
:param distro: "debian" or "alpine"
:paral slim: slim version
:param slim: slim version
:return: tuple (version, tags) where version if the version of WeeChat
to build and tags is a list of tag arguments for command line,
for example: ['-t', 'weechat:3.0-alpine',
Expand Down Expand Up @@ -111,25 +162,61 @@ def get_version_tags(
tags_args = []
for tag in reversed(tags):
for suffix in suffixes:
tags_args.extend(
[
"-t",
tags_args.append(
f"weechat:{tag}{suffix}",
"-t",
f"weechat/weechat:{tag}{suffix}",
]
)
return (version, tags_args)


def run_command(command: List[str], dry_run: bool) -> None:
"""
Prints the command to run and runs it if 'dry_run=False'
:param command: ["docker", "images"]
:param dry_run: True
:return: None
"""
print(f'Running: {" ".join(command)}')
if not dry_run:
try:
subprocess.run(command, check=False) # nosec
except KeyboardInterrupt:
pass
except Exception as exc: # pylint: disable=broad-except
print(exc)


def main() -> None:
"""Main function."""
build_args = ["buildx", "build"]
args = get_parser().parse_args()
slim = ["--build-arg", "SLIM=1"] if args.slim else []
version, tags = get_version_tags(args.version, args.distro, args.slim)

if args.builder == "docker":
docker_tags = []
for tag in tags:
docker_tags.extend(["-t", f"{args.registry_project}/{tag}"])
tags = docker_tags
if args.push:
build_args.append("--push")

podman_slim = ""
if args.slim:
podman_slim = "-slim"
podman_manifest_image = f"localhost/weechat:{version}" \
+ f"-{args.distro}{podman_slim}-manifest"
podman_tags = [tag for tag in tags if tag.startswith("weechat:")]

if args.builder == "podman":
build_args = ["build", "--jobs", "0"]
tags = ["--manifest", podman_manifest_image]

command = [
f"{args.builder}",
"build",
*build_args,
"--platform",
",".join(args.platforms),
"-f",
f"{args.distro}/Containerfile",
"--build-arg",
Expand All @@ -138,14 +225,27 @@ def main() -> None:
*tags,
".",
]
print(f'Running: {" ".join(command)}')
if not args.dry_run:
try:
subprocess.run(command, check=False) # nosec
except KeyboardInterrupt:
pass
except Exception as exc: # pylint: disable=broad-except
print(exc)
run_command(command, args.dry_run)

if args.builder == "podman":
command = [
f"{args.builder}",
"tag",
podman_manifest_image,
*[f"localhost/{tag}" for tag in podman_tags]
]
run_command(command, args.dry_run)

if args.push:
for tag in podman_tags:
command = [
f"{args.builder}",
"manifest",
"push",
f"localhost/{tag}",
f"{args.registry_project}/{tag}"
]
run_command(command, args.dry_run)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit e8c30e6

Please sign in to comment.