Skip to content

Commit e8c30e6

Browse files
committed
Support cross platform builds
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 ```
1 parent 04f7bab commit e8c30e6

File tree

6 files changed

+231
-56
lines changed

6 files changed

+231
-56
lines changed

Makefile

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,73 @@
1616
#
1717

1818
BUILDER ?= docker
19+
PLATFORMS ?= linux/amd64
1920
VERSION ?= latest
2021
IMAGE ?= weechat
22+
REGISTRY ?= docker.io
23+
REGISTRY_PROJECT ?= weechat
24+
REGISTRY_PROJECT_URL = $(REGISTRY)/$(REGISTRY_PROJECT)
25+
REGISTRY_IMAGE = $(REGISTRY_PROJECT_URL)/$(IMAGE)
26+
27+
ALPINE_BASE_IMAGE = alpine:3.15
28+
DEBIAN_BASE_IMAGE = debian:bullseye-slim
29+
30+
ifeq ($(strip $(PUSH)),true)
31+
PUSH_ARG = "--push"
32+
endif
2133

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

2436
all: debian
2537

2638
all-images: debian debian-slim alpine alpine-slim
2739

28-
debian:
29-
./build.py -b "$(BUILDER)" -d "debian" "$(VERSION)"
40+
debian: weechat-multiarch
41+
./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "debian" "$(VERSION)"
42+
43+
debian-slim: weechat-multiarch
44+
./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "debian" --slim "$(VERSION)"
3045

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

34-
alpine:
35-
./build.py -b "$(BUILDER)" -d "alpine" "$(VERSION)"
49+
alpine-slim: weechat-multiarch
50+
./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "alpine" --slim "$(VERSION)"
3651

37-
alpine-slim:
38-
./build.py -b "$(BUILDER)" -d "alpine" --slim "$(VERSION)"
52+
weechat-multiarch:
53+
if [ "$(BUILDER)" == "docker" ]; then \
54+
docker buildx ls | grep weechat-multiarch || docker buildx create --name weechat-multiarch ; \
55+
docker buildx use weechat-multiarch; \
56+
fi
3957

40-
test-container:
41-
"$(BUILDER)" run "$(IMAGE)" weechat --version
42-
"$(BUILDER)" run "$(IMAGE)" weechat-headless --version
58+
test-images:
59+
for PLATFORM in $(PLATFORMS); do \
60+
for TAG in $(VERSION)-{debian,alpine}{,-slim}; do \
61+
REGISTRY_IMAGE_TAG=$(REGISTRY_IMAGE):$$TAG; \
62+
echo "Testing $$REGISTRY_IMAGE_TAG for $$PLATFORM platform" ; \
63+
$(BUILDER) pull --platform $$PLATFORM $$REGISTRY_IMAGE_TAG &>/dev/null; \
64+
echo -n "weechat --version: "; \
65+
$(BUILDER) run --rm --platform $$PLATFORM $$REGISTRY_IMAGE_TAG --version; \
66+
echo -n "weechat-headless --version: "; \
67+
$(BUILDER) run --rm --platform $$PLATFORM --entrypoint /usr/bin/weechat-headless $$REGISTRY_IMAGE_TAG --version ; \
68+
$(BUILDER) rmi $$REGISTRY_IMAGE_TAG &>/dev/null; \
69+
done; \
70+
done
4371

72+
clean-images:
73+
if [ "$(BUILDER)" == "podman" ]; then \
74+
buildah rm --all; \
75+
$(BUILDER) images --format="{{.Repository}}:{{.Tag}}" localhost/$(IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \
76+
$(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(REGISTRY_PROJECT)/$(IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \
77+
$(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(ALPINE_BASE_IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \
78+
$(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(DEBIAN_BASE_IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \
79+
$(BUILDER) image prune -f; \
80+
fi
81+
if [ "$(BUILDER)" == "docker" ]; then \
82+
$(BUILDER) buildx rm weechat-multiarch; \
83+
$(BUILDER) rmi moby/buildkit:buildx-stable-1
84+
fi
85+
4486
lint: flake8 pylint mypy bandit
4587

4688
flake8:

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ The slim version includes all plugins except these ones:
3131
- scripting languages: perl, python, ruby, lua, tcl, guile, php
3232
- spell
3333

34+
## Supported platforms
35+
36+
It's possible to build for different linux platforms other than amd64,
37+
the only prerequisite is to have `qemu-user-static` package installed
38+
39+
- `linux/386`
40+
- `linux/amd64`
41+
- `linux/arm/v6`
42+
- `linux/arm/v7`
43+
- `linux/arm64`
44+
- `linux/ppc64le`
45+
- `linux/s390x`
46+
47+
3448
## Install from Docker Hub
3549

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

89+
Build all images with latest stable version of WeeChat for AMD64 and ARM64 platforms
90+
and push them to the `docker.io/weechat` registry project:
91+
92+
```
93+
$ make PLATFORMS="linux/amd64 linux/arm64" REGISTRY="docker.io" REGISTRY_PROJECT="weechat" PUSH=true all-images
94+
```
95+
7596
Build an Alpine-based image with Podman, slim version, WeeChat 3.6:
7697

7798
```

alpine/Containerfile

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,12 @@ ENV VERSION="${VERSION}"
1818
ARG SLIM=""
1919
ENV SLIM="${SLIM}"
2020

21-
ARG HOME="/home/user"
21+
ARG HOME="/weechat"
2222
ENV HOME="${HOME}"
2323

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

27-
# create a user
28-
RUN set -eux ; \
29-
adduser -u 1001 -D -h "$HOME" user ; \
30-
mkdir -p "$HOME/.weechat" ; \
31-
mkdir -p "$HOME/.config/weechat" ; \
32-
mkdir -p "$HOME/.local/share/weechat" ; \
33-
mkdir -p "$HOME/.cache/weechat" ; \
34-
chown -R user:user "$HOME"
35-
3627
# ==== build ====
3728

3829
FROM base as build
@@ -126,6 +117,8 @@ RUN set -eux ; \
126117
libgcrypt \
127118
ncurses-libs \
128119
ncurses-terminfo \
120+
shadow \
121+
setpriv \
129122
tzdata \
130123
zlib \
131124
zstd \
@@ -151,8 +144,17 @@ COPY --from=build /opt/weechat /opt/weechat
151144
RUN ln -sf /opt/weechat/bin/weechat /usr/bin/weechat
152145
RUN ln -sf /opt/weechat/bin/weechat-headless /usr/bin/weechat-headless
153146

154-
WORKDIR $HOME
147+
# create a user
148+
RUN set -eux ; \
149+
adduser -u 1000 -D -h "$HOME" weechat; \
150+
mkdir -p "$HOME/.weechat" ; \
151+
mkdir -p "$HOME/.config/weechat" ; \
152+
mkdir -p "$HOME/.local/share/weechat" ; \
153+
mkdir -p "$HOME/.cache/weechat" ; \
154+
chown -R weechat:weechat "$HOME"
155155

156-
USER user
156+
ADD ../run.sh /
157+
158+
WORKDIR $HOME
157159

158-
CMD ["weechat"]
160+
ENTRYPOINT [ "/run.sh" ]

build.py

Lines changed: 119 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,54 @@
1818

1919
"""Build WeeChat container image."""
2020

21-
from typing import List, Sequence, Tuple
21+
from typing import List, Sequence, Tuple, Dict
2222

2323
import argparse
2424
import urllib.request
2525
import subprocess # nosec
2626

27+
BUILDERS: Sequence[str] = (
28+
"docker",
29+
"podman",
30+
)
31+
2732
DISTROS: Sequence[str] = (
2833
"debian",
2934
"alpine",
3035
)
3136

37+
SUPPORTED_PLATFORMS: Dict[str, Dict[str, List[str]]] = {
38+
"linux": {
39+
"386": [],
40+
"amd64": [],
41+
"arm": ["v6", "v7"],
42+
"arm64": [],
43+
"ppc64le": [],
44+
"s390x": [],
45+
},
46+
}
47+
48+
49+
def generate_valid_platforms() -> list[str]:
50+
"""Return the list of supported platforms."""
51+
valid_platforms = []
52+
for os_name, archs in SUPPORTED_PLATFORMS.items():
53+
for arch, variants in archs.items():
54+
if not variants:
55+
valid_platforms.append(f"{os_name}/{arch}")
56+
for variant in variants:
57+
valid_platforms.append(f"{os_name}/{arch}/{variant}")
58+
return valid_platforms
59+
3260

3361
def get_parser() -> argparse.ArgumentParser:
3462
"""Return the command line parser."""
3563
parser = argparse.ArgumentParser(description="Build of WeeChat container")
3664
parser.add_argument(
3765
"-b",
3866
"--builder",
39-
default="docker",
67+
choices=BUILDERS,
68+
default=BUILDERS[0],
4069
help=(
4170
"program used to build the container image, "
4271
"like docker (default) or podman"
@@ -47,7 +76,29 @@ def get_parser() -> argparse.ArgumentParser:
4776
"--distro",
4877
choices=DISTROS,
4978
default=DISTROS[0],
50-
help="base Linux distribution for the container",
79+
help="base Linux distribution for the container, default %(default)s",
80+
)
81+
parser.add_argument(
82+
"-p",
83+
"--platforms",
84+
default="linux/amd64",
85+
choices=generate_valid_platforms(),
86+
nargs="+",
87+
help="build platforms",
88+
)
89+
parser.add_argument(
90+
"--push",
91+
action="store_true",
92+
default=False,
93+
help=(
94+
"push the images to the registry, default: %(default)s"
95+
),
96+
)
97+
parser.add_argument(
98+
"-r",
99+
"--registry-project",
100+
default="docker.io/weechat",
101+
help="registry project, default: %(default)s",
51102
)
52103
parser.add_argument(
53104
"-n",
@@ -81,7 +132,7 @@ def get_version_tags(
81132
82133
:param version: x.y, x.y.z, "latest", "stable", "devel"
83134
:param distro: "debian" or "alpine"
84-
:paral slim: slim version
135+
:param slim: slim version
85136
:return: tuple (version, tags) where version if the version of WeeChat
86137
to build and tags is a list of tag arguments for command line,
87138
for example: ['-t', 'weechat:3.0-alpine',
@@ -111,25 +162,61 @@ def get_version_tags(
111162
tags_args = []
112163
for tag in reversed(tags):
113164
for suffix in suffixes:
114-
tags_args.extend(
115-
[
116-
"-t",
165+
tags_args.append(
117166
f"weechat:{tag}{suffix}",
118-
"-t",
119-
f"weechat/weechat:{tag}{suffix}",
120-
]
121167
)
122168
return (version, tags_args)
123169

124170

171+
def run_command(command: List[str], dry_run: bool) -> None:
172+
"""
173+
Prints the command to run and runs it if 'dry_run=False'
174+
175+
:param command: ["docker", "images"]
176+
:param dry_run: True
177+
:return: None
178+
"""
179+
print(f'Running: {" ".join(command)}')
180+
if not dry_run:
181+
try:
182+
subprocess.run(command, check=False) # nosec
183+
except KeyboardInterrupt:
184+
pass
185+
except Exception as exc: # pylint: disable=broad-except
186+
print(exc)
187+
188+
125189
def main() -> None:
126190
"""Main function."""
191+
build_args = ["buildx", "build"]
127192
args = get_parser().parse_args()
128193
slim = ["--build-arg", "SLIM=1"] if args.slim else []
129194
version, tags = get_version_tags(args.version, args.distro, args.slim)
195+
196+
if args.builder == "docker":
197+
docker_tags = []
198+
for tag in tags:
199+
docker_tags.extend(["-t", f"{args.registry_project}/{tag}"])
200+
tags = docker_tags
201+
if args.push:
202+
build_args.append("--push")
203+
204+
podman_slim = ""
205+
if args.slim:
206+
podman_slim = "-slim"
207+
podman_manifest_image = f"localhost/weechat:{version}" \
208+
+ f"-{args.distro}{podman_slim}-manifest"
209+
podman_tags = [tag for tag in tags if tag.startswith("weechat:")]
210+
211+
if args.builder == "podman":
212+
build_args = ["build", "--jobs", "0"]
213+
tags = ["--manifest", podman_manifest_image]
214+
130215
command = [
131216
f"{args.builder}",
132-
"build",
217+
*build_args,
218+
"--platform",
219+
",".join(args.platforms),
133220
"-f",
134221
f"{args.distro}/Containerfile",
135222
"--build-arg",
@@ -138,14 +225,27 @@ def main() -> None:
138225
*tags,
139226
".",
140227
]
141-
print(f'Running: {" ".join(command)}')
142-
if not args.dry_run:
143-
try:
144-
subprocess.run(command, check=False) # nosec
145-
except KeyboardInterrupt:
146-
pass
147-
except Exception as exc: # pylint: disable=broad-except
148-
print(exc)
228+
run_command(command, args.dry_run)
229+
230+
if args.builder == "podman":
231+
command = [
232+
f"{args.builder}",
233+
"tag",
234+
podman_manifest_image,
235+
*[f"localhost/{tag}" for tag in podman_tags]
236+
]
237+
run_command(command, args.dry_run)
238+
239+
if args.push:
240+
for tag in podman_tags:
241+
command = [
242+
f"{args.builder}",
243+
"manifest",
244+
"push",
245+
f"localhost/{tag}",
246+
f"{args.registry_project}/{tag}"
247+
]
248+
run_command(command, args.dry_run)
149249

150250

151251
if __name__ == "__main__":

0 commit comments

Comments
 (0)