Skip to content

Commit 6300602

Browse files
committed
Initial commit
0 parents  commit 6300602

File tree

3 files changed

+552
-0
lines changed

3 files changed

+552
-0
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Copyright 2026 ETH Zurich and University of Bologna.
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Philippe Sauter <phsauter@iis.ee.ethz.ch>
6+
#
7+
#
8+
# Workflow to build a smaller CI-friendlier version of the iic-osic-tools image
9+
# aimed at digital workflows only (eg github.com/pulp-platform/croc/)
10+
#
11+
# Required repository configs on Github
12+
# - DOCKERHUB_USERNAME Docker Hub username -> public
13+
# - DOCKERHUB_TOKEN Docker Hub access token (rw) added as a secret
14+
#
15+
# The resulting image is pushed to:
16+
# - docker.io/<DOCKERHUB_USERNAME>/oseda-ci:<source_tag>
17+
# - docker.io/<DOCKERHUB_USERNAME>/oseda-ci:latest
18+
19+
name: Build and push
20+
21+
# Trigger options
22+
# Push to main: rebuild the image (e.g. after Dockerfile edits)
23+
# using DEFAULT_SOURCE_TAG defined below.
24+
#
25+
# Tag push 20YY.MM: rebuild using that tag as the source, producing
26+
# a matching versioned image on Docker Hub.
27+
#
28+
# workflow_dispatch: manual trigger; caller supplies the source tag.
29+
30+
on:
31+
push:
32+
branches: [main]
33+
tags: ['20[0-9][0-9].[0-9][0-9]'] # matches iic-osic-tools versioning
34+
workflow_dispatch:
35+
inputs:
36+
source_tag:
37+
description: 'iic-osic-tools source tag to build from (e.g. YYYY.MM)'
38+
required: true
39+
default: '2025.12'
40+
41+
env:
42+
DEFAULT_SOURCE_TAG: '2025.12'
43+
44+
jobs:
45+
46+
prepare:
47+
runs-on: ubuntu-latest
48+
outputs:
49+
source_tag: ${{ steps.meta.outputs.source_tag }}
50+
image: ${{ steps.meta.outputs.image }}
51+
steps:
52+
- name: Resolve source tag and image name
53+
id: meta
54+
run: |
55+
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
56+
# Tag push: use the git tag itself as the source version.
57+
tag="$GITHUB_REF_NAME"
58+
elif [[ -n "${{ inputs.source_tag }}" ]]; then
59+
# Manual dispatch: use the caller-supplied tag.
60+
tag="${{ inputs.source_tag }}"
61+
else
62+
# Branch push (e.g. Dockerfile tweak): fall back to default.
63+
tag="$DEFAULT_SOURCE_TAG"
64+
fi
65+
echo "source_tag=$tag" >> "$GITHUB_OUTPUT"
66+
echo "image=${{ vars.DOCKERHUB_USERNAME }}/oseda-ci" >> "$GITHUB_OUTPUT"
67+
68+
# Build each platform on a native runner
69+
# - amd64 runs on the standard GitHub-hosted runner
70+
# - arm64 runs on GitHub's native arm64 runner (ubuntu-24.04-arm)
71+
build:
72+
name: Build (${{ matrix.platform }})
73+
needs: prepare
74+
runs-on: ${{ matrix.runner }}
75+
strategy:
76+
fail-fast: false
77+
matrix:
78+
include:
79+
- platform: linux/amd64
80+
runner: ubuntu-latest
81+
- platform: linux/arm64
82+
runner: ubuntu-24.04-arm
83+
84+
steps:
85+
- name: Checkout
86+
uses: actions/checkout@v4
87+
88+
- name: Sanitise platform name
89+
# 'linux/amd64' -> 'linux-amd64' (slashes are invalid in artifacts)
90+
id: plat
91+
run: echo "pair=$(echo '${{ matrix.platform }}' | tr '/' '-')" >> "$GITHUB_OUTPUT"
92+
93+
- name: Set up Docker Buildx
94+
uses: docker/setup-buildx-action@v3
95+
96+
- name: Log in to Docker Hub
97+
uses: docker/login-action@v3
98+
with:
99+
username: ${{ vars.DOCKERHUB_USERNAME }}
100+
password: ${{ secrets.DOCKERHUB_TOKEN }}
101+
102+
- name: Detect base image from source
103+
id: base
104+
run: |
105+
# Read the base-image label from the source image for the current platform
106+
# ubuntu:noble as the fallback
107+
BASE=$(docker buildx imagetools inspect \
108+
--raw \
109+
"hpretl/iic-osic-tools:${{ needs.prepare.outputs.source_tag }}" \
110+
--format '{{ (index .Image.Config.Labels "org.opencontainers.image.base.name") }}' \
111+
2>/dev/null || true)
112+
echo "image=${BASE:-ubuntu:noble}" >> "$GITHUB_OUTPUT"
113+
114+
- name: Build and push
115+
id: build
116+
uses: docker/build-push-action@v6
117+
with:
118+
context: .
119+
platforms: ${{ matrix.platform }}
120+
# Push without a tag for now; the image is only addressable by its content digest
121+
push: true
122+
outputs: >-
123+
type=image,
124+
name=${{ needs.prepare.outputs.image }},
125+
push-by-digest=true,
126+
name-canonical=true
127+
build-args: |
128+
SOURCE_IMAGE=hpretl/iic-osic-tools:${{ needs.prepare.outputs.source_tag }}
129+
BASE_IMAGE=${{ steps.base.outputs.image }}
130+
# Per-platform cache stored in the registry so it persists across runners and workflow runs
131+
cache-from: type=registry,ref=${{ needs.prepare.outputs.image }}:cache-${{ steps.plat.outputs.pair }}
132+
cache-to: type=registry,ref=${{ needs.prepare.outputs.image }}:cache-${{ steps.plat.outputs.pair }},mode=max
133+
134+
- name: Record digest for manifest assembly
135+
run: |
136+
mkdir -p /tmp/digests
137+
touch "/tmp/digests/${DIGEST#sha256:}"
138+
env:
139+
DIGEST: ${{ steps.build.outputs.digest }}
140+
141+
- name: Upload digest artifact
142+
uses: actions/upload-artifact@v4
143+
with:
144+
name: digest-${{ steps.plat.outputs.pair }}
145+
path: /tmp/digests/*
146+
if-no-files-found: error
147+
retention-days: 1
148+
149+
# Assemble the multi-arch manifest from digests
150+
merge:
151+
name: Merge manifests
152+
needs: [prepare, build]
153+
runs-on: ubuntu-latest
154+
155+
steps:
156+
- name: Download all digest artifacts
157+
uses: actions/download-artifact@v4
158+
with:
159+
path: /tmp/digests
160+
pattern: digest-*
161+
merge-multiple: true # flatten into a single directory
162+
163+
- name: Set up Docker Buildx
164+
uses: docker/setup-buildx-action@v3
165+
166+
- name: Log in to Docker Hub
167+
uses: docker/login-action@v3
168+
with:
169+
username: ${{ vars.DOCKERHUB_USERNAME }}
170+
password: ${{ secrets.DOCKERHUB_TOKEN }}
171+
172+
- name: Create and push multi-arch manifest
173+
working-directory: /tmp/digests
174+
run: |
175+
docker buildx imagetools create \
176+
--tag ${{ needs.prepare.outputs.image }}:${{ needs.prepare.outputs.source_tag }} \
177+
--tag ${{ needs.prepare.outputs.image }}:latest \
178+
$(printf '${{ needs.prepare.outputs.image }}@sha256:%s ' *)
179+
180+
- name: Inspect final manifest
181+
run: docker buildx imagetools inspect ${{ needs.prepare.outputs.image }}:latest

Dockerfile

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Copyright 2026 ETH Zurich and University of Bologna.
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Philippe Sauter <phsauter@iis.ee.ethz.ch>
6+
#
7+
#
8+
# Digital design image for CIs and headless use based on iic-osic-tools
9+
#
10+
# Includes:
11+
# EDA tools : yosys (+ eqy/sby/mcy/yices2 + slang plugin),
12+
# slang (standalone SV linter), verilator, OpenROAD,
13+
# KLayout, RISC-V GNU toolchain
14+
# Lint/format: black, flake8, isort, mypy (Python)
15+
# clang-format (C++)
16+
# slang --lint-only, verilator --lint-only (SystemVerilog)
17+
#
18+
# Uses the iic-osic-tools image on Docker Hub as a build-time source
19+
# Only the tool directories we need are copied into a fresh
20+
# ubuntu:noble runtime layer, so the final image contains
21+
# none of the GUI/VNC/desktop stack and only necessary tools.
22+
#
23+
# Runtime apt packages are derived automatically: ldd maps each tool
24+
# binary's shared-library dependency tree back to dpkg package names.
25+
# Only interpreters and executables (python3, perl, gcc, …) that the
26+
# tools spawn at runtime still need to be listed explicitly,
27+
# as they are not shared libraries and ldd cannot see them.
28+
29+
ARG SOURCE_IMAGE=hpretl/iic-osic-tools:2025.12
30+
FROM ${SOURCE_IMAGE} AS source
31+
32+
# Derive the needed apt-managed packages
33+
# 1. Find all executable files and shared libraries of the EDA tools.
34+
# 2. Run ldd on every found file to enumerate the full dependency tree
35+
# (discard errors for non-ELF files such as scripts).
36+
# 3. Keep only paths under /usr/lib or /lib, these are apt-managed.
37+
# /usr/local is excluded because we copy those libs separately;
38+
# /foss is excluded because we copy the tool dirs themselves.
39+
# 4. Map each .so path back to its owning dpkg package and deduplicate.
40+
RUN find \
41+
/foss/tools/yosys \
42+
/foss/tools/slang \
43+
/foss/tools/verilator \
44+
/foss/tools/openroad \
45+
/foss/tools/klayout \
46+
/foss/tools/riscv-gnu-toolchain/bin \
47+
-type f \( -executable -o -name "*.so*" \) \
48+
| xargs ldd 2>/dev/null \
49+
| awk '/=>/ { print $3 }' \
50+
| grep -E '^/(usr/lib|lib)/' \
51+
| sort -u \
52+
| xargs dpkg -S 2>/dev/null \
53+
| cut -d: -f1 \
54+
| sort -u \
55+
> /tmp/apt-packages.txt
56+
57+
# Record all pip-installed packages as a constraints file.
58+
# Used in the final stage with --constraint so that any package we
59+
# choose to install is pinned to exactly the version from this image.
60+
RUN pip3 freeze > /tmp/pip-constraints.txt
61+
62+
# Base image: defaults to ubuntu:noble but can be overridden at build time.
63+
# In CI the base is read from the org.opencontainers.image.base.name label
64+
# of the source image so this image always tracks the same base as iic-osic-tools.
65+
ARG BASE_IMAGE=ubuntu:noble
66+
FROM ${BASE_IMAGE}
67+
68+
ENV DEBIAN_FRONTEND=noninteractive \
69+
TZ=Europe/Vienna \
70+
LC_ALL=en_US.UTF-8 \
71+
LANG=en_US.UTF-8 \
72+
TOOLS=/foss/tools \
73+
PDK_ROOT=/foss/pdks \
74+
DESIGNS=/foss/designs \
75+
# Disable the PEP 668 "externally managed environment" restriction.
76+
# In a container this guard is pointless.
77+
PIP_BREAK_SYSTEM_PACKAGES=1
78+
79+
COPY --from=source /tmp/apt-packages.txt /tmp/
80+
COPY --from=source /tmp/pip-constraints.txt /tmp/
81+
82+
RUN apt-get update \
83+
# Install the shared-library packages derived from the ldd scan.
84+
&& xargs apt-get install -y --no-install-recommends < /tmp/apt-packages.txt \
85+
# Install interpreters and tools that the EDA tools invoke at runtime.
86+
# These are executables (not shared libraries) so ldd cannot find them.
87+
&& apt-get install -y --no-install-recommends \
88+
ca-certificates \
89+
locales \
90+
tzdata \
91+
git \
92+
wget \
93+
curl \
94+
python3 \
95+
python3-pip \
96+
python3-venv \
97+
perl \
98+
ruby \
99+
ruby-irb \
100+
tcl \
101+
tcllib \
102+
gcc \
103+
g++ \
104+
make \
105+
clang-format \
106+
&& locale-gen en_US.UTF-8 \
107+
&& rm -rf /var/lib/apt/lists/* /tmp/apt-packages.txt
108+
109+
# OpenROAD depends on several libraries that are not packaged in Ubuntu
110+
# and are built from source in the base image.
111+
COPY --from=source /usr/local/lib /usr/local/lib
112+
RUN ldconfig
113+
114+
ENV HOME=/headless
115+
RUN mkdir -p ${TOOLS} ${PDK_ROOT} ${DESIGNS} ${HOME}
116+
117+
# Copy tools from iic-osic-tools to this image
118+
COPY --from=source ${TOOLS}/yosys ${TOOLS}/yosys/
119+
COPY --from=source ${TOOLS}/slang ${TOOLS}/slang/
120+
COPY --from=source ${TOOLS}/verilator ${TOOLS}/verilator/
121+
COPY --from=source ${TOOLS}/riscv-gnu-toolchain ${TOOLS}/riscv-gnu-toolchain/
122+
COPY --from=source ${TOOLS}/openroad ${TOOLS}/openroad/
123+
COPY --from=source ${TOOLS}/klayout ${TOOLS}/klayout/
124+
125+
# Unified bin directory with symlinks to all tools
126+
COPY --from=source ${TOOLS}/bin ${TOOLS}/bin/
127+
128+
# profile.d: sourced by login shells
129+
# sets PATH, PYTHONPATH, LD_LIBRARY_PATH.
130+
COPY --from=source /etc/profile.d/iic-osic-tools-setup.sh \
131+
/etc/profile.d/iic-osic-tools-setup.sh
132+
133+
# .bashrc: sourced by interactive shells — re-exports the same env vars
134+
# and provides all aliases (ll, gss, k, …) and the custom prompt.
135+
COPY --from=source ${HOME}/.bashrc ${HOME}/.bashrc
136+
137+
# tool version manifest
138+
COPY --from=source /tool_metadata.yml /tool_metadata.yml
139+
140+
# Default environment setup
141+
142+
ENV RISCV=${TOOLS}/riscv-gnu-toolchain
143+
ENV PATH=\
144+
${TOOLS}/bin:\
145+
${TOOLS}/yosys/bin:\
146+
${TOOLS}/slang/bin:\
147+
${TOOLS}/verilator/bin:\
148+
${TOOLS}/riscv-gnu-toolchain/bin:\
149+
${TOOLS}/openroad/bin:\
150+
${TOOLS}/klayout:\
151+
${PATH}
152+
153+
ENV LD_LIBRARY_PATH=${TOOLS}/klayout
154+
155+
# Python paths:
156+
# - pyosys: the Yosys Python API lives under the yosys share tree
157+
# - klayout Python module (pymod) for scripting and DRC/LVS
158+
ENV PYTHONPATH=${TOOLS}/yosys/share/yosys/python3:${TOOLS}/klayout/pymod
159+
160+
# Python tools (linter and formatter)
161+
RUN pip3 install --no-cache-dir --constraint /tmp/pip-constraints.txt \
162+
black \
163+
flake8 \
164+
isort \
165+
mypy \
166+
&& rm /tmp/pip-constraints.txt
167+
168+
RUN chown -R 1000:1000 ${HOME} ${DESIGNS}
169+
USER 1000:1000
170+
WORKDIR ${DESIGNS}

0 commit comments

Comments
 (0)