Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
699b709
see if I can mount /mnt on github actions too
jiridanek Sep 23, 2025
a754613
install zig
jiridanek Sep 23, 2025
02e48bb
Add progress display for wget in Zig installation
jiridanek Sep 23, 2025
8f99628
Configure sandbox to support cross-compilation with Zig for s390x-lin…
jiridanek Sep 23, 2025
454049f
Fix Dockerfile.cpu mount check logic in `ubi9-python-3.12` runtime
jiridanek Sep 23, 2025
e5d2538
Modify sandbox to set `CC` and `CXX` as environment variables for Zig…
jiridanek Sep 23, 2025
d077f19
Simplify Zig paths in sandbox for cross-compilation environment varia…
jiridanek Sep 23, 2025
48a4a52
Update target in sandbox for s390x to specify glibc version (2.34).
jiridanek Sep 23, 2025
0c41a8a
Address deprecation warning and unsupported arg in s390x build process
jiridanek Sep 23, 2025
2bf813d
Add wrapper scripts for Zig cross-compilation and adjust s390x sandbo…
jiridanek Sep 23, 2025
728baa9
Add `zigcc` wrapper for s390x cross-compilation, including `Makefile`…
jiridanek Sep 24, 2025
40b2c23
Update sandbox to correctly set `CC` and `CXX` for Zig, add cache bus…
jiridanek Sep 24, 2025
eea6333
Add `ninja-build` dependency for s390x and ppc64le in `ubi9-python-3.…
jiridanek Sep 24, 2025
4a4c752
without zig, before 10 min
jiridanek Sep 24, 2025
7ce40ed
Bump Zig to version 0.15.2 in Makefile
jiridanek Nov 20, 2025
5cba801
with zig again
jiridanek Nov 20, 2025
ed37088
Remove `zigcc` wrapper script for s390x cross-compilation
jiridanek Nov 20, 2025
9d4971e
Add dynamic target resolution for `zigcc` based on architecture
jiridanek Nov 20, 2025
b0cd250
Refactor `zigcc` to use environment-based architecture detection, enh…
jiridanek Nov 20, 2025
c441a6f
Clean up unused environment variables in `sandbox.py`.
jiridanek Nov 20, 2025
e51c1a0
Improve error messages in `zigcc.go` for clarity and update `ppc64le`…
jiridanek Nov 21, 2025
1f0cac9
Enhance `zigcc` wrapper for improved cross-compilation: update `getTa…
jiridanek Nov 21, 2025
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
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,36 @@ refresh-lock-files:
scan-image-vulnerabilities:
python ci/security-scan/quay_security_analysis.py

ARCH := $(shell uname -m)
ifeq ($(ARCH),amd64)
ARCH := x86_64
else ifeq ($(ARCH),arm64)
ARCH := aarch64
endif

ZIG_VERSION := 0.15.2
ZIG_BINARY := zig-$(ZIG_VERSION)

bin/zig-$(ZIG_VERSION):
@echo "Installing Zig $(ZIG_VERSION)..."
TMPDIR=$(shell mktemp -d)
wget --progress=dot:giga https://ziglang.org/download/$(ZIG_VERSION)/zig-$(ARCH)-linux-$(ZIG_VERSION).tar.xz
tar -xJf zig-$(ARCH)-linux-$(ZIG_VERSION).tar.xz -C $$TMPDIR --strip-components=1
rm -rf zig-$(ARCH)-linux-$(ZIG_VERSION).tar.xz

mv $$TMPDIR bin/zig-$(ZIG_VERSION)
@echo "Zig installed as bin/zig-$(ZIG_VERSION)"

# This should be .PHONY because it's an alias/action
.PHONY: install-zig
install-zig: bin/zig-$(ZIG_VERSION)
@echo "Zig is ready to use!"

# Another .PHONY target for cleanup
.PHONY: clean-zig
clean-zig:
rm -rf bin/zig-$(ZIG_VERSION)

# This is used primarily for gen_gha_matrix_jobs.py to we know the set of all possible images we may want to build
.PHONY: all-images
ifeq ($(RELEASE_PYTHON_VERSION), 3.12)
Expand Down
4 changes: 3 additions & 1 deletion runtimes/minimal/ubi9-python-3.12/Dockerfile.cpu
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WORKDIR /opt/app-root/bin
# OS Packages needs to be installed as root
USER 0

RUN ls /mnt && true

### BEGIN upgrade first to avoid fixable vulnerabilities
# If we have a Red Hat subscription prepared, refresh it
RUN /bin/bash <<'EOF'
Expand All @@ -39,7 +41,7 @@ ARCH=$(uname -m)
echo "Detected architecture: $ARCH"
PACKAGES="perl mesa-libGL skopeo"
if [ "$ARCH" = "s390x" ] || [ "$ARCH" = "ppc64le" ]; then
PACKAGES="$PACKAGES gcc g++ make openssl-devel autoconf automake libtool cmake"
PACKAGES="$PACKAGES gcc g++ ninja-build openssl-devel autoconf automake libtool cmake"
fi
dnf install -y --setopt=keepcache=1 $PACKAGES
EOF
Expand Down
94 changes: 93 additions & 1 deletion scripts/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,35 @@ def main() -> int:

with tempfile.TemporaryDirectory(delete=True) as tmpdir:
setup_sandbox(prereqs, pathlib.Path(tmpdir))
command = [arg if arg != "{};" else tmpdir for arg in args.remaining[1:]]
additional_arguments = [
# Mount the zigcc utility
f"--volume={os.getcwd()}/bin/zig-0.15.2:/mnt",
f"--env=ZIGCC_ARCH={args.platform.split('/')[1]}",
"--unsetenv=ZIGCC_ARCH",
# CMake heeds these
"--env=CC=/mnt/cc",
"--env=CXX=/mnt/c++",
"--unsetenv=CC",
"--unsetenv=CXX",
# CMake ignores these
"--env=AR=/mnt/ar",
"--env=RANLIB=/mnt/ranlib",
"--env=STRIP=/mnt/strip",
"--unsetenv=AR",
"--unsetenv=RANLIB",
"--unsetenv=STRIP",
# Workaround for a s390x compilation issue
"--env=CXXFLAGS=-Dundefined=64",
"--unsetenv=CXXFLAGS",

tmpdir,
]
command = []
for arg in args.remaining[1:]:
if arg == "{};":
command.extend(additional_arguments)
else:
command.append(arg)
print(f"running {command=}")
try:
subprocess.check_call(command)
Expand All @@ -56,13 +84,77 @@ def main() -> int:
return err.returncode
return 0

"""
Downloading jedi
× Failed to build `pyzmq==27.1.0`
├─▶ The build backend returned an error
╰─▶ Call to `scikit_build_core.build.build_wheel` failed (exit status: 1)
[stdout]
*** scikit-build-core 0.11.6 using CMake 3.26.5 (wheel)
*** Configuring CMake...
loading initial cache file /tmp/tmpf9bnfh5o/build/CMakeInit.txt
-- Configuring incomplete, errors occurred!
[stderr]
CMake Error at /usr/share/cmake/Modules/CMakeDetermineCCompiler.cmake:49
(message):
Could not find compiler set in environment variable CC:
/mnt/zig-0.15.1/zig cc -target s390x-linux-gnu.
Call Stack (most recent call first):
CMakeLists.txt:2 (project)
"""

"""
creating build/temp.linux-s390x-cpython-312/psutil/arch/linux
/mnt/zig cc -target s390x-linux-gnu -fno-strict-overflow
-Wsign-compare -DDYNAMIC_ANNOTATIONS_ENABLED=1 -DNDEBUG
-O2 -fexceptions -g -grecord-gcc-switches -pipe
-Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2
-Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong
-m64 -march=z14 -mtune=z15 -fasynchronous-unwind-tables
-fstack-clash-protection -O2 -fexceptions -g -grecord-gcc-switches
-pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2
-Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong
-m64 -march=z14 -mtune=z15 -fasynchronous-unwind-tables
-fstack-clash-protection -O2 -fexceptions -g -grecord-gcc-switches
-pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2
-Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong
-m64 -march=z14 -mtune=z15 -fasynchronous-unwind-tables
-fstack-clash-protection -fPIC -DPSUTIL_POSIX=1 -DPSUTIL_SIZEOF_PID_T=4
-DPSUTIL_VERSION=700 -DPy_LIMITED_API=0x03060000
-DPSUTIL_LINUX=1 -I/tmp/.tmpWlL4ZP/builds-v0/.tmpOwAhw2/include
-I/usr/include/python3.12 -c psutil/_psutil_common.c -o
build/temp.linux-s390x-cpython-312/psutil/_psutil_common.o
[stderr]
/tmp/.tmpWlL4ZP/builds-v0/.tmpOwAhw2/lib64/python3.12/site-packages/setuptools/dist.py:759:
SetuptoolsDeprecationWarning: License classifiers are deprecated.
!!

********************************************************************************
Please consider removing the following classifiers in favor of a
SPDX license expression:
License :: OSI Approved :: BSD License
See
https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license
for details.

********************************************************************************
!!
self._finalize_license_expression()
error: unsupported preprocessor arg: -D_FORTIFY_SOURCE
"""

def buildinputs(
dockerfile: pathlib.Path | str,
platform: Literal["linux/amd64", "linux/arm64", "linux/s390x", "linux/ppc64le"] = "linux/amd64"
) -> list[pathlib.Path]:
if not (ROOT_DIR / "bin/buildinputs").exists():
subprocess.check_call([MAKE, "bin/buildinputs"], cwd=ROOT_DIR)
if not (ROOT_DIR / "bin/zig-0.15.2").exists():
subprocess.check_call([MAKE, "bin/zig-0.15.2"], cwd=ROOT_DIR)
if not (ROOT_DIR / "bin/zig-0.15.2/zigcc").exists():
subprocess.check_call([MAKE, "build"], cwd=ROOT_DIR / "scripts/zigcc")
for alias in ["cc", "c++", "ar", "llvm-ar", "ranlib", "llvm-ranlib", "strip", "llvm-strip"]:
shutil.copy(ROOT_DIR / "scripts/zigcc/bin/zigcc", ROOT_DIR / "bin/zig-0.15.2" / alias)
stdout = subprocess.check_output([ROOT_DIR / "bin/buildinputs", str(dockerfile)],
text=True, cwd=ROOT_DIR,
env={"TARGETPLATFORM": platform, **os.environ})
Expand Down
21 changes: 21 additions & 0 deletions scripts/zigcc/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.PHONY: build test clean

build: bin/zigcc

# always build for linux and the machine's native architecture
bin/zigcc: *.go go.mod
GOOS=linux go build -o $@ -ldflags="-s -w" -v ./...

test:
go test -v ./...

fmt:
go fmt ./...

vet:
go vet ./...

clean:
go clean
rm -f bin/*
rmdir bin
27 changes: 27 additions & 0 deletions scripts/zigcc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# zigcc

_Launcher for `zig cc`, `zig c++`, and related subcommands for more efficient cross-compilation_

## Cross-compilation overview

### Qemu-user-static

Docker/Podman can perform cross-compilation using `qemu-user-static`.
The idea is to install the various `qemu-user` binaries as interpreters for foreign architecture binaries.
Launching such binary will then automatically run it under qemu interpreter.

Docker is uniquely suitable to run binaries like this, because container images bring all dependencies with them.

### Traditional cross-compilation

For CMake, I can imagine an approach which involves installing a cross compiler and mounting arm64 docker image to provide arm64 environment with libraries.
<https://cmake.org/cmake/help/book/mastering-cmake/chapter/Cross%20Compiling%20With%20CMake.html>

### Zig

The `zig cc` command bundles clang in a way that simplifies its usage for cross compilation,
<https://zig.news/kristoff/cross-compile-a-c-c-project-with-zig-3599>

## Credits

This is inspired by <https://github.com/skupperproject/skupper-router/pull/1100>
3 changes: 3 additions & 0 deletions scripts/zigcc/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module zigcc

go 1.24
Comment on lines +1 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Target a released Go toolchain

go 1.24 is not available yet, so current builders (Go ≤1.22/1.23) will abort with “requires go >=1.24”. Drop to the latest released version you actually need (e.g. 1.22).

-go 1.24
+go 1.22
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
module zigcc
go 1.24
module zigcc
go 1.22
🤖 Prompt for AI Agents
In scripts/zigcc/go.mod around lines 1 to 3, the module specifies "go 1.24"
which is not yet released and will abort builds on current Go toolchains; change
the go directive to an actual released version your project needs (for example
"go 1.22" or the minimum released version that supports the features you use),
update any CI/docker builders to match that version if necessary, and run go mod
tidy/build locally to verify compatibility.

97 changes: 97 additions & 0 deletions scripts/zigcc/zigcc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
)

const (
zig = "/mnt/zig"
)

func getTarget() string {
var arch string
switch os.Getenv("ZIGCC_ARCH") {
case "amd64":
arch = "x86_64"
case "arm64":
arch = "aarch64"
case "ppc64le":
arch = "ppc64le"
case "s390x":
arch = "s390x"
default:
fmt.Fprintf(os.Stderr, "Error: unknown architecture: %s\n", os.Getenv("ZIGCC_ARCH"))
os.Exit(1)
}

// target glibc 2.28 or newer (supports FORTIFY_SOURCE)
return arch + "-linux-gnu.2.34"
}

func processArg0(arg0 string) (string, error) {
switch arg0 {
case "cc":
return "cc", nil
case "c++":
return "c++", nil

// `llvm-` prefix so that CMake finds it
// https://gitlab.kitware.com/cmake/cmake/-/issues/23554
// https://gitlab.kitware.com/cmake/cmake/-/issues/18712#note_1006035
// ../../libtool: line 1887: /mnt/ar: No such file or directory
case "ar", "llvm-ar":
return "ar", nil
case "ranlib", "llvm-ranlib":
return "ranlib", nil
case "strip", "llvm-strip":
return "strip", nil

default:
return "", fmt.Errorf("unknown wrapper name: %s", arg0)
}
}

func processArgs(args []string) []string {
newArgs := make([]string, 0, len(args))
for _, arg := range args {
// deal with -Wp,-D_FORTIFY_SOURCE=2:
// this comes in https://github.com/giampaolo/psutil/blob/master/setup.py#L254
// build defaults to using python's flags and they are the RHEL fortified ones
if strings.HasPrefix(arg, "-Wp,") {
newArgs = append(newArgs, strings.Split(arg, ",")[1:]...)
} else {
newArgs = append(newArgs, arg)
}
}
return newArgs
}

func main() {
arg0 := filepath.Base(os.Args[0])
subcommand, err := processArg0(arg0)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

target := getTarget()

newArgs := []string{
zig,
subcommand,
}
if subcommand == "cc" || subcommand == "c++" {
newArgs = append(newArgs, "-target", target)
}
newArgs = append(newArgs, processArgs(os.Args[1:])...)

env := os.Environ()
if err := syscall.Exec(newArgs[0], newArgs, env); err != nil {
fmt.Fprintf(os.Stderr, "Error executing zig: %v\n", err)
os.Exit(1)
}
}
35 changes: 35 additions & 0 deletions scripts/zigcc/zigcc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"fmt"
"reflect"
"testing"
)

func TestProcessWp(t *testing.T) {
args := []string{"-Wp,-D_FORTIFY_SOURCE=2"}
newArgs := processArgs(args)
if !reflect.DeepEqual(newArgs, []string{"-D_FORTIFY_SOURCE=2"}) {
t.Fatalf("expected -DFOO=bar, got %v", newArgs)
}
for _, tc := range []struct {
args []string
expected []string
}{
{
args: []string{"-Wp,-D_FORTIFY_SOURCE=2"},
expected: []string{"-D_FORTIFY_SOURCE=2"},
},
{
args: []string{"-Wp,-DNDEBUG,-D_FORTIFY_SOURCE=2"},
expected: []string{"-DNDEBUG", "-D_FORTIFY_SOURCE=2"},
},
} {
t.Run(fmt.Sprint(tc.args), func(t *testing.T) {
newArgs := processArgs(tc.args)
if !reflect.DeepEqual(newArgs, tc.expected) {
t.Fatalf("expected %#v, got %#v", tc.expected, newArgs)
}
})
}
}
Loading