Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support cross platform builds #6

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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