diff --git a/.ci/matrix.py b/.ci/matrix.py new file mode 100755 index 0000000..c527014 --- /dev/null +++ b/.ci/matrix.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""Helpers for the CI. + +Reads ``.ci/matrix.yml`` (the single source of truth for which images exist) +and answers questions for the GitHub Actions workflows: + + matrix.py all + Print, on one line, a JSON array of every image. Used as the + ``strategy.matrix`` for the build workflow. Each element looks like:: + + {"os": "ubuntu", "version": "24.04", "dir": "ubuntu/24.04", + "base_tag": "ubuntu-24.04", "context": "ubuntu", + "dockerfile": "ubuntu/Dockerfile", "target": "full", + "build_args": "UBUNTU_VERSION=24.04", "addons": "cmake-4"} + + ``target`` is the build stage for the base image ("" = final stage). + Each add-on in ``addons`` is itself a build stage (--target). Both + ``build_args`` and ``addons`` are space-separated strings so they can be + looped over in shell directly. + + matrix.py image + Resolve a release tag such as ``ubuntu-24.04-2.1.0`` to the image it + refers to and print shell ``key='value'`` assignments to stdout:: + + dir='ubuntu/24.04' + base_tag='ubuntu-24.04' + semver='2.1.0' + context='ubuntu' + dockerfile='ubuntu/Dockerfile' + target='full' + build_args='UBUNTU_VERSION=24.04' + addons='cmake-4' + + Intended to be consumed with ``eval "$(python .ci/matrix.py image …)"``. + + matrix.py publish-matrix + Print, on one line, a JSON array of ``{"tag": "-"}`` + objects for every image in the matrix. Used by the scheduled workflow + to build the publish job matrix with date-stamped immutable tags:: + + [{"tag":"ubuntu-24.04-2026.06.23"},{"tag":"ubuntu-22.04-2026.06.23"}] +""" + +from __future__ import annotations + +import json +import os +import re +import shlex +import sys + +import yaml + +HERE = os.path.dirname(os.path.abspath(__file__)) +MATRIX_FILE = os.path.join(HERE, "matrix.yml") + +SEMVER_RE = re.compile(r"^(?P.+)-(?P\d+\.\d+\.\d+)$") + + +def load_images(): + with open(MATRIX_FILE, encoding="utf-8") as handle: + data = yaml.safe_load(handle) + images = data.get("images") or [] + result = [] + for img in images: + os_name = str(img["os"]) + version = str(img["version"]) + directory = f"{os_name}/{version}" + context = str(img.get("context") or f"{directory}/base") + dockerfile = str(img.get("dockerfile") or f"{context}/Dockerfile") + target = str(img.get("target") or "") + build_args = " ".join( + f"{k}={v}" for k, v in (img.get("build_args") or {}).items() + ) + addons = [str(a) for a in (img.get("addons") or [])] + result.append( + { + "os": os_name, + "version": version, + "dir": directory, + "base_tag": f"{os_name}-{version}", + "context": context, + "dockerfile": dockerfile, + "target": target, + "build_args": build_args, + "addons": " ".join(addons), + } + ) + return result + + +def cmd_all(): + print(json.dumps(load_images(), separators=(",", ":"))) + + +def cmd_publish_matrix(date: str): + images = load_images() + result = [{"tag": f"{img['base_tag']}-{date}"} for img in images] + print(json.dumps(result, separators=(",", ":"))) + + +def cmd_image(tag: str): + match = SEMVER_RE.match(tag) + if not match: + sys.exit( + f"error: tag '{tag}' is not of the form -- " + f"(e.g. ubuntu-24.04-2.1.0)" + ) + prefix = match.group("prefix") + semver = match.group("semver") + + for img in load_images(): + if img["base_tag"] == prefix: + print(f"dir={shlex.quote(img['dir'])}") + print(f"base_tag={shlex.quote(img['base_tag'])}") + print(f"semver={shlex.quote(semver)}") + print(f"context={shlex.quote(img['context'])}") + print(f"dockerfile={shlex.quote(img['dockerfile'])}") + print(f"target={shlex.quote(img['target'])}") + print(f"build_args={shlex.quote(img['build_args'])}") + print(f"addons={shlex.quote(img['addons'])}") + return + + valid = ", ".join(img["base_tag"] for img in load_images()) + sys.exit( + f"error: no image matches tag prefix '{prefix}'. " + f"Known images: {valid}" + ) + + +def main(argv): + if len(argv) >= 2 and argv[1] == "all": + cmd_all() + elif len(argv) >= 3 and argv[1] == "image": + cmd_image(argv[2]) + elif len(argv) >= 3 and argv[1] == "publish-matrix": + cmd_publish_matrix(argv[2]) + else: + sys.exit(f"usage: {argv[0]} all | image | publish-matrix ") + + +if __name__ == "__main__": + main(sys.argv) diff --git a/.ci/matrix.yml b/.ci/matrix.yml new file mode 100644 index 0000000..c0a3e87 --- /dev/null +++ b/.ci/matrix.yml @@ -0,0 +1,97 @@ +# Declares every image this repository builds. +# +# Each entry is one OS / OS-version. Every OS uses a single multi-stage +# Dockerfile at /Dockerfile (e.g. ubuntu/Dockerfile) covering all of its +# versions: the base image is the `full` stage and add-ons are extra stages. +# +# Per-image fields: +# context build context directory (default: //base) +# dockerfile path to the Dockerfile (default: /Dockerfile) +# build_args map of --build-arg values, e.g. the version (default: none) +# target build stage for the BASE image (default: final stage) +# addons list of add-ons. Each add-on is a build STAGE (--target) in the +# image's own Dockerfile (default: none) +# The shared-Dockerfile images set context/dockerfile/target explicitly (e.g. +# all Ubuntu versions use ubuntu/Dockerfile and differ only by UBUNTU_VERSION; +# the base is the `full` stage and add-ons such as `cmake-4` are extra stages). +# +# Tag grammar (see README.md): +# base moving : - e.g. ubuntu-24.04 +# base immutable : -- e.g. ubuntu-24.04-2.1.0 +# addon moving : -- e.g. ubuntu-24.04-cmake-4 +# addon immutable : --- e.g. ubuntu-24.04-cmake-4-2.1.0 +# +# is THIS repository's version (the recipe), independent of the +# OpenModelica version. Pushing the git tag `--` releases +# the base image and all of its add-ons (see RELEASING.md). + +images: + # All Ubuntu versions share the multi-stage ubuntu/Dockerfile; only + # UBUNTU_VERSION differs. The base image is the `full` stage; add-ons are + # further stages (e.g. `cmake-4`). + - os: ubuntu + version: "26.04" + context: ubuntu + dockerfile: ubuntu/Dockerfile + target: full + build_args: + UBUNTU_VERSION: "26.04" + addons: + - rust + + - os: ubuntu + version: "24.04" + context: ubuntu + dockerfile: ubuntu/Dockerfile + target: full + build_args: + UBUNTU_VERSION: "24.04" + addons: + - cmake-4 + + - os: ubuntu + version: "22.04" + context: ubuntu + dockerfile: ubuntu/Dockerfile + target: full + build_args: + UBUNTU_VERSION: "22.04" + addons: [] + + # Placeholders — not implemented yet (see /Dockerfile). Each OS uses the + # same single multi-stage Dockerfile layout as Ubuntu. Uncomment and flesh out + # once the corresponding Dockerfile is real, so CI starts building them. + # + # - os: debian + # version: "13" + # context: debian + # dockerfile: debian/Dockerfile + # target: full + # build_args: + # DEBIAN_VERSION: "13" + # addons: [] + # + # - os: almalinux + # version: "9" + # context: almalinux + # dockerfile: almalinux/Dockerfile + # target: full + # build_args: + # ALMALINUX_VERSION: "9" + # addons: [] + # + # - os: arch + # version: "rolling" + # context: arch + # dockerfile: arch/Dockerfile + # target: full + # addons: [] + # + # - os: opensuse-leap + # version: "16.0" + # context: opensuse-leap + # dockerfile: opensuse-leap/Dockerfile + # target: full + # build_args: + # LEAP_VERSION: "16.0" + # addons: [] diff --git a/.ci/publish.sh b/.ci/publish.sh new file mode 100755 index 0000000..485cfa5 --- /dev/null +++ b/.ci/publish.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# +# Build and push one image (base + all its add-ons) to a registry. +# +# Used by both publish.yml (GHCR) and publish-nexus.yml (Nexus). The base image +# is pushed under a moving tag (-) and an immutable tag +# (--). Each add-on is a build STAGE (--target) in the same +# Dockerfile and is pushed under its own moving + immutable tags; shared layers +# come from the build cache, so the base is effectively built only once. +# +# Required environment variables: +# REGISTRY Image repository, e.g. ghcr.io/openmodelica/build-deps +# TAG Release tag, e.g. ubuntu-24.04-2.1.0 +# SIGN "true" to cosign-sign every pushed tag (keyless OIDC), else "false" +set -euo pipefail + +: "${REGISTRY:?REGISTRY is required}" +: "${TAG:?TAG is required}" +SIGN="${SIGN:-false}" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Resolve the tag to base_tag / semver / context / dockerfile / target / +# build_args / addons. +eval "$(python3 "${SCRIPT_DIR}/matrix.py" image "${TAG}")" + +PUSHED_TAGS=() + +build_and_push() { + # $1 = moving tag, $2 = immutable tag, remaining args go to `docker buildx`. + local moving="$1" immutable="$2" + shift 2 + echo "::group::Building ${moving}" + docker buildx build \ + --pull \ + --file "${dockerfile}" \ + --tag "${REGISTRY}:${moving}" \ + --tag "${REGISTRY}:${immutable}" \ + --push \ + "$@" \ + "${context}" + echo "::endgroup::" + PUSHED_TAGS+=("${REGISTRY}:${moving}" "${REGISTRY}:${immutable}") +} + +# Common build-args (e.g. UBUNTU_VERSION) apply to every stage. +common_args=() +for kv in ${build_args}; do + common_args+=(--build-arg "${kv}") +done + +# 1. Base image: the image's `target` stage (or the final stage). +base_target_arg=() +[ -n "${target}" ] && base_target_arg=(--target "${target}") +build_and_push "${base_tag}" "${base_tag}-${semver}" \ + "${common_args[@]}" "${base_target_arg[@]}" + +# 2. Add-ons: each is a --target stage in the same Dockerfile. +for addon in ${addons}; do + build_and_push "${base_tag}-${addon}" "${base_tag}-${addon}-${semver}" \ + "${common_args[@]}" --target "${addon}" +done + +# 3. Optionally sign every pushed tag (GHCR / cosign keyless). +if [ "${SIGN}" = "true" ]; then + for image in "${PUSHED_TAGS[@]}"; do + echo "Signing ${image}" + cosign sign --yes "${image}" + done +fi + +printf '%s\n' "${PUSHED_TAGS[@]}" diff --git a/.ci/requirements.txt b/.ci/requirements.txt new file mode 100644 index 0000000..86d967e --- /dev/null +++ b/.ci/requirements.txt @@ -0,0 +1 @@ +pyyaml==6.0.3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 802707e..107e224 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,37 +1,273 @@ -name: Build Docker Image +name: Build, Release & Publish + +# Single pipeline so a release created here can trigger publishing in the SAME +# run. +# +# discover ─▶ build (all images, no push) +# └─▶ release (tag only) ─▶ publish-ghcr + publish-nexus +# +# Release/publish are keyed on image tags: --, +# e.g. ubuntu-24.04-2.1.0. +# +# Triggers: +# schedule Weekly rolling rebuild (Sunday night → Monday 00:00 UTC). +# Publishes every image with a date-based immutable tag +# (YYYY.MM.DD) in addition to the moving tag, without creating +# a GitHub Release. +# push tag Full release: GitHub Release + immutable semver tag. +# workflow_dispatch Re-publish a single image tag on demand. on: + schedule: + - cron: '0 0 * * 1' # Monday 00:00 UTC — Sunday-night rolling rebuild push: branches: - main - - 'releases/**' tags: - - v* + - '*-*.*.*' pull_request: branches: - main - - 'releases/**' + workflow_dispatch: + inputs: + tag: + description: 'Image tag to (re)publish (e.g. ubuntu-24.04-2.1.0)' + required: true + +permissions: + contents: read jobs: + discover: + name: Discover images + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set.outputs.matrix }} + publish_matrix: ${{ steps.publish_tags.outputs.matrix }} + steps: + - name: Check out the repo + uses: actions/checkout@v7 + + - name: Install dependencies + run: pip install -r .ci/requirements.txt + + - name: Compute build matrix + id: set + run: echo "matrix=$(python .ci/matrix.py all)" >> "$GITHUB_OUTPUT" + + - name: Compute publish matrix + id: publish_tags + run: | + case "${{ github.event_name }}" in + schedule) + DATE=$(date +%Y.%m.%d) + echo "matrix=$(python3 .ci/matrix.py publish-matrix "${DATE}")" >> "$GITHUB_OUTPUT" + ;; + workflow_dispatch) + echo 'matrix=[{"tag":"${{ github.event.inputs.tag }}"}]' >> "$GITHUB_OUTPUT" + ;; + push) + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + echo 'matrix=[{"tag":"${{ github.ref_name }}"}]' >> "$GITHUB_OUTPUT" + else + echo 'matrix=[]' >> "$GITHUB_OUTPUT" + fi + ;; + *) + echo 'matrix=[]' >> "$GITHUB_OUTPUT" + ;; + esac + build: - name: Build Dockerfile + name: Build ${{ matrix.image.os }}-${{ matrix.image.version }} + needs: discover + runs-on: ubuntu-latest + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + image: ${{ fromJson(needs.discover.outputs.matrix) }} + steps: + - name: Check out the repo + uses: actions/checkout@v7 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build base image and add-ons + env: + BASE_TAG: ${{ matrix.image.base_tag }} + CONTEXT: ${{ matrix.image.context }} + DOCKERFILE: ${{ matrix.image.dockerfile }} + TARGET: ${{ matrix.image.target }} + BUILD_ARGS: ${{ matrix.image.build_args }} + ADDONS: ${{ matrix.image.addons }} + run: | + set -euo pipefail + + # Common build-args (e.g. UBUNTU_VERSION) apply to every stage. + common_args=() + for kv in ${BUILD_ARGS}; do common_args+=(--build-arg "${kv}"); done + + # Base image: the image's `target` stage (or the final stage). + base_target_arg=() + [ -n "${TARGET}" ] && base_target_arg=(--target "${TARGET}") + echo "::group::Building base ${BASE_TAG}" + docker buildx build \ + --file "${DOCKERFILE}" \ + "${common_args[@]}" "${base_target_arg[@]}" \ + --tag "local/build-deps:${BASE_TAG}" \ + --load \ + "${CONTEXT}" + echo "::endgroup::" + + # Add-ons: each is a --target stage in the same Dockerfile. + for addon in ${ADDONS}; do + echo "::group::Building add-on ${addon}" + docker buildx build \ + --file "${DOCKERFILE}" \ + "${common_args[@]}" --target "${addon}" \ + --tag "local/build-deps:${BASE_TAG}-${addon}" \ + --load \ + "${CONTEXT}" + echo "::endgroup::" + done + + release: + name: Create GitHub Release + needs: build + if: ${{ startsWith(github.ref, 'refs/tags/') }} runs-on: ubuntu-latest - timeout-minutes: 60 + permissions: + contents: write steps: - name: Check out the repo - uses: actions/checkout@v7.0.0 + uses: actions/checkout@v7 - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v6 + - name: Install dependencies + run: pip install -r .ci/requirements.txt + + - name: Create or update release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Validate tag + python .ci/matrix.py image "${GITHUB_REF_NAME}" >/dev/null + # Per-image releases are never marked as the repository "latest": + # there is no single newest image in the monorepo. + if gh release view "${GITHUB_REF_NAME}" &>/dev/null; then + gh release edit "${GITHUB_REF_NAME}" \ + --title "${GITHUB_REF_NAME}" \ + --generate-notes \ + --latest=false + else + gh release create "${GITHUB_REF_NAME}" \ + --title "${GITHUB_REF_NAME}" \ + --generate-notes \ + --verify-tag \ + --latest=false + fi + + publish-ghcr: + name: Publish ${{ matrix.image.tag }} to GHCR + needs: [discover, build, release] + # Runs on: a release tag, a manual re-publish dispatch, or the weekly schedule. + # publish_matrix is [] for PR and branch-push runs, so those are excluded. + if: >- + ${{ always() + && needs.build.result == 'success' + && (needs.release.result == 'success' || needs.release.result == 'skipped') + && needs.discover.outputs.publish_matrix != '[]' }} + runs-on: ubuntu-latest + timeout-minutes: 90 + permissions: + contents: read + packages: write + id-token: write # needed for signing the images with GitHub OIDC Token + strategy: + fail-fast: false + matrix: + image: ${{ fromJson(needs.discover.outputs.publish_matrix) }} + env: + REGISTRY: ghcr.io/openmodelica/build-deps + TAG: ${{ matrix.image.tag }} + steps: + - name: Check out the repo + uses: actions/checkout@v7 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Install dependencies + run: pip install -r .ci/requirements.txt + + - name: Install cosign + uses: sigstore/cosign-installer@v4.1.2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4.2.0 with: - images: ghcr.io/openmodelica/build-deps + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Build Docker image - uses: docker/build-push-action@v7 + - name: Build, push and sign image (base + add-ons) + env: + SIGN: "true" + run: ./.ci/publish.sh + + - name: Verify signatures + run: | + set -euo pipefail + eval "$(python .ci/matrix.py image "${TAG}")" + CERT_ID="https://github.com/${GITHUB_REPOSITORY}/.github/workflows/build.yml@${GITHUB_REF}" + CERT_ISSUER="https://token.actions.githubusercontent.com" + tags=("${base_tag}" "${base_tag}-${semver}") + for addon in ${addons}; do + tags+=("${base_tag}-${addon}" "${base_tag}-${addon}-${semver}") + done + for t in "${tags[@]}"; do + echo "Verifying ${REGISTRY}:${t}" + cosign verify \ + --certificate-identity="${CERT_ID}" \ + --certificate-oidc-issuer="${CERT_ISSUER}" \ + "${REGISTRY}:${t}" + done + + publish-nexus: + name: Publish ${{ matrix.image.tag }} to Nexus + needs: [discover, build, release] + if: >- + ${{ always() + && needs.build.result == 'success' + && (needs.release.result == 'success' || needs.release.result == 'skipped') + && needs.discover.outputs.publish_matrix != '[]' }} + runs-on: ubuntu-latest + timeout-minutes: 90 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + image: ${{ fromJson(needs.discover.outputs.publish_matrix) }} + env: + REGISTRY: docker.openmodelica.org/build-deps + TAG: ${{ matrix.image.tag }} + steps: + - name: Check out the repo + uses: actions/checkout@v7 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to Nexus Docker registry + uses: docker/login-action@v4.2.0 with: - context: . - file: ./Dockerfile - tags: ${{ steps.meta.outputs.tags }} - annotations: ${{ steps.meta.outputs.annotations }} - push: false + registry: docker.openmodelica.org + username: ${{ secrets.NEXUS_OPENMODELICABOT_USER }} + password: ${{ secrets.NEXUS_OPENMODELICABOT_PASSWORD }} + + - name: Build and push image (base + add-ons) + env: + SIGN: "false" + run: ./.ci/publish.sh diff --git a/.github/workflows/publish-nexus.yml b/.github/workflows/publish-nexus.yml deleted file mode 100644 index 3f39a03..0000000 --- a/.github/workflows/publish-nexus.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Publish Docker Image to Nexus - -on: - release: - types: [published, edited] - workflow_dispatch: - inputs: - tag: - description: 'Image tag to publish (e.g. v1.22.3)' - required: true - -permissions: - contents: read - -jobs: - push_to_nexus: - name: Push Docker image to Nexus (docker.openmodelica.org) - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - name: Check out the repo - uses: actions/checkout@v7.0.0 - - - name: Login to Nexus Docker registry - uses: docker/login-action@v4.2.0 - with: - registry: docker.openmodelica.org - username: ${{ secrets.NEXUS_OPENMODELICABOT_USER }} - password: ${{ secrets.NEXUS_OPENMODELICABOT_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v6 - with: - images: docker.openmodelica.org/build-deps - tags: | - type=ref,event=tag - type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} - - - name: Build and push Docker image - uses: docker/build-push-action@v7 - with: - context: . - file: ./Dockerfile - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - annotations: ${{ steps.meta.outputs.annotations }} - push: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 248f19f..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Publish Docker Image - -on: - release: - types: [published, edited] - workflow_dispatch: - inputs: - tag: - description: 'Image tag to publish (e.g. v1.22.3)' - required: true - -# Required for cosign keyless (OIDC) to mint tokens -permissions: - contents: read - packages: write - id-token: write # needed for signing the images with GitHub OIDC Token - -jobs: - push_to_registry: - name: Push Docker image to GitHub Container registry - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - name: Check out the repo - uses: actions/checkout@v7.0.0 - - - name: Install cosign - uses: sigstore/cosign-installer@v4.1.2 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v4.2.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v6 - with: - images: ghcr.io/openmodelica/build-deps - tags: | - type=ref,event=tag - type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} - - - name: Build and push Docker image - uses: docker/build-push-action@v7 - with: - context: . - file: ./Dockerfile - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - annotations: ${{ steps.meta.outputs.annotations }} - push: true - - - name: Sign the images with GitHub OIDC Token (tag-based) - env: - TAGS: ${{ steps.meta.outputs.tags }} - run: | - set -euo pipefail - [ -n "${TAGS:-}" ] || { echo "No tags found"; exit 1; } - images="" - while IFS= read -r tag; do - tag="$(echo "$tag" | xargs)" # trim whitespace - [ -z "$tag" ] && continue - images+="${tag} " - echo "Signing tag: ${tag}" - cosign sign --yes "${tag}" - done < <(echo "${TAGS:-}" | tr ',' '\n') - - - name: Verify signatures (tag-based) - env: - TAGS: ${{ steps.meta.outputs.tags }} - run: | - set -euo pipefail - [ -n "${TAGS:-}" ] || { echo "No tags found"; exit 1; } - CERT_ID="https://github.com/${{ github.repository }}/.github/workflows/publish.yml@${{ github.ref }}" - CERT_ISSUER="https://token.actions.githubusercontent.com" - while IFS= read -r tag; do - tag="$(echo "$tag" | xargs)" - [ -z "$tag" ] && continue - echo "Verifying tag: ${tag}" - cosign verify \ - --certificate-identity="${CERT_ID}" \ - --certificate-oidc-issuer="${CERT_ISSUER}" \ - "${tag}" - done < <(echo "${TAGS:-}" | tr ',' '\n') diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index b30e8a4..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Create Release - -on: - push: - tags: - - 'v*' - -permissions: - contents: write - -jobs: - release: - name: Create GitHub Release - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v7.0.0 - - - name: Create or update release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # Only mark as latest when the tag is on the default branch - BASE_BRANCH=$(gh api repos/${{ github.repository }} --jq '.default_branch') - ON_DEFAULT=$(git branch -r --contains "${{ github.ref_name }}" | grep -qE "origin/${BASE_BRANCH}$" && echo true || echo false) - if gh release view "${{ github.ref_name }}" &>/dev/null; then - gh release edit "${{ github.ref_name }}" \ - --title "${{ github.ref_name }}" \ - --generate-notes \ - --latest="${ON_DEFAULT}" - else - gh release create "${{ github.ref_name }}" \ - --title "${{ github.ref_name }}" \ - --generate-notes \ - --verify-tag \ - --latest="${ON_DEFAULT}" - fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76088b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.venv/ +.vscode/ +CLAUDE.md diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b8d4338..0000000 --- a/Dockerfile +++ /dev/null @@ -1,117 +0,0 @@ -FROM ubuntu:noble - -# Image / OCI metadata -LABEL maintainer="AnHeuermann" -LABEL description="OpenModelica build-deps Docker Image" -LABEL organization="OpenModelica" - -LABEL org.opencontainers.image.vendor="OpenModelica" -LABEL org.opencontainers.image.authors="AnHeuermann" -LABEL org.opencontainers.image.version="v1.26.1" -LABEL org.opencontainers.image.description="OpenModelica build-deps Docker Image " -LABEL org.opencontainers.image.source="https://github.com/OpenModelica/build-deps" -LABEL org.opencontainers.image.license="MIT" - -ENV SHELL=/bin/bash - -# Ensure DEBIAN_FRONTEND is only set during build -ARG DEBIAN_FRONTEND=noninteractive - -# Install OpenModelica build-deps -RUN apt-get update \ - && apt-get upgrade -qy \ - && apt-get dist-upgrade -qy \ - && apt-get install -qy \ - ca-certificates \ - curl \ - gnupg \ - lsb-release \ - && curl -fsSL https://build.openmodelica.org/apt/openmodelica.asc | gpg --dearmor -o /usr/share/keyrings/openmodelica-keyring.gpg \ - && echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openmodelica-keyring.gpg] https://build.openmodelica.org/apt \ - $(cat /etc/os-release | grep "\(UBUNTU\\|DEBIAN\\|VERSION\)_CODENAME" | sort | cut -d= -f 2 | head -1) \ - nightly" | tee /etc/apt/sources.list.d/openmodelica.list > /dev/null \ - && echo \ - "deb-src [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openmodelica-keyring.gpg] https://build.openmodelica.org/apt \ - $(cat /etc/os-release | grep "\(UBUNTU\\|DEBIAN\\|VERSION\)_CODENAME" | sort | cut -d= -f 2 | head -1) \ - nightly" | tee -a /etc/apt/sources.list.d/openmodelica.list > /dev/null \ - && apt-get update \ - && apt-get build-dep -qy openmodelica \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Install additional dependencies -# - tools to build the User's Guide -# - Qt5, Qt6 packages -RUN apt-get update \ - && apt-get install -qy \ - aspell \ - bibtex2html \ - bison \ - ccache \ - clang-tools \ - devscripts \ - docker.io \ - doxygen \ - equivs \ - flex \ - git \ - gnuplot-nox \ - inkscape \ - intel-opencl-icd \ - latexmk \ - libcurl4-gnutls-dev \ - libmldbm-perl \ - libqt6core5compat6-dev \ - libqt6opengl6-dev \ - libqt6openglwidgets6 \ - libqt6svg6-dev \ - locales \ - ocl-icd-opencl-dev \ - opencl-headers \ - pandoc \ - pocl-opencl-icd \ - poppler-utils \ - python3-pip \ - python3-venv \ - qt6-base-dev \ - qt6-httpserver-dev \ - qt6-scxml-dev \ - qt6-tools-dev \ - qt6-tools-dev-tools \ - qt6-webengine-dev \ - qt6-httpserver-dev \ - qt6-websockets-dev \ - qtwebengine5-dev \ - subversion \ - texlive-base \ - texlive-bibtex-extra \ - texlive-lang-greek \ - texlive-latex-extra \ - unzip \ - wget \ - xsltproc \ - xvfb \ - zip \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Install Python packages in a default virtual environment -# Use permalink for doc/UsersGuide/source/requirements.txt to keep builds -# deterministic. -RUN python3 -m venv /opt/venv -ENV PATH="/opt/venv/bin:$PATH" -RUN pip install --no-cache-dir \ - junit_xml \ - lxml \ - ompython==3.6.0 \ - PyGithub \ - simplejson \ - svgwrite \ - && pip install --no-cache-dir -r \ - https://raw.githubusercontent.com/OpenModelica/OpenModelica/9c0dc9a8ab50ba652109584cb3fecaef86640b66/doc/UsersGuide/source/requirements.txt - -# Set locale -ENV LANGUAGE=en_US:en -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 diff --git a/README.md b/README.md index 1e525a2..eb31590 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,163 @@ -# OpenModelica build-deps Docker Image +# OpenModelica build-deps Docker Images -[![Build Docker Image](https://github.com/OpenModelica/build-deps/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/OpenModelica/build-deps/actions/workflows/build.yml) -[![Publish Docker Image](https://github.com/OpenModelica/build-deps/actions/workflows/publish.yml/badge.svg)](https://github.com/OpenModelica/build-deps/actions/workflows/publish.yml) +[![Build, Release & Publish][badge-build-img]][workflow-build] The Docker images used to build and deploy -[OpenModelica](https://github.com/OpenModelica/OpenModelica) with -[Jenkins](https://test.openmodelica.org/jenkins/). +[OpenModelica][openmodelica] with +[Jenkins][jenkins]. -## Structure of the Repository +Images are published to: -Each minor version of the Dockerfile corresponds to a OpenModelica minor version -and has its own branch. Each branch has tags for each patch version. +- `ghcr.io/openmodelica/build-deps` (GitHub Container Registry) +- `docker.openmodelica.org/build-deps` (Nexus) -When creating a release form a tag the -[workflow](./.github/workflows/publish.yml) will publish the Docker image to -[GitHub Container registry](https://github.com/OpenModelica/openmodelica-build-deps/pkgs/container/build-deps). +## Structure of the Repository -### Ubuntu based Images +Every image lives on `main`, keyed by **operating system and OS version** rather +than by OpenModelica version. Each image is a **base** plus optional, layered +**add-ons**, so the heavy common tooling is built once and reused. + +```text +main +├── ubuntu/ +│ └── Dockerfile # multi-stage: ALL Ubuntu versions + add-ons +├── debian/Dockerfile # placeholder (not implemented yet) +├── almalinux/Dockerfile # placeholder (not implemented yet) +├── arch/Dockerfile # placeholder (not implemented yet) +└── .ci/ + ├── matrix.yml # source of truth: which images exist + ├── matrix.py # matrix.yml -> CI matrix / tag lookup + └── publish.sh # build + push one image (base + add-ons) +``` -- 24.04 Noble: - - [releases/v1.26](https://github.com/OpenModelica/build-deps/tree/releases/v1.26) - - [releases/v1.26-cmake4](https://github.com/OpenModelica/build-deps/tree/releases/v1.26-cmake4) -- 22.04 Jammy: - - [releases/v1.22](https://github.com/OpenModelica/build-deps/tree/releases/v1.22) - - [releases/v1.22-qtwebengine](https://github.com/OpenModelica/build-deps/tree/releases/v1.22-qtwebengine), replaced by [releases/v1.22](https://github.com/OpenModelica/build-deps/tree/releases/v1.22) v1.22.3 or higher - - [releases/v1.24-qt5qt6](https://github.com/OpenModelica/build-deps/tree/releases/v1.22-qtwebengine), replaced by [releases/v1.22](https://github.com/OpenModelica/build-deps/tree/releases/v1.22) v1.22.3 or higher -- 20.04 Focal: [releases/v1.21](https://github.com/OpenModelica/build-deps/tree/releases/v1.21) -- 18.04 Bionic + cmake: [releases/v1.16-cmake](https://github.com/OpenModelica/build-deps/tree/releases/v1.16-cmake) -- 18.04 Bionic: [releases/v1.16](https://github.com/OpenModelica/build-deps/tree/releases/v1.16) +> **Status:** only the **Ubuntu** images are implemented. Debian, AlmaLinux and +> Arch are empty placeholders (an `/Dockerfile` with a TODO header) and are +> intentionally left out of [.ci/matrix.yml][matrix-yml] until implemented, +> so CI does not try to build them. + +- **Base image** — one per OS/OS-version. Contains everything needed to build + OpenModelica (distro packages + common tooling: TeX, Qt, Python venv, + ccache, …). This is what most CI jobs use. +- **Add-on image** — the base plus *one* thing the distro package manager can't + provide or that needs a pinned version (e.g. CMake 4). Realised as an extra + build **stage** (`FROM` the base stage) in the same Dockerfile, so shared + layers are reused from cache. + +Every OS uses a single **multi-stage** Dockerfile at `/Dockerfile` covering +all of its versions (the Debian/AlmaLinux/Arch placeholders follow this too). +All Ubuntu versions build from [ubuntu/Dockerfile][ubuntu-dockerfile]: + +- the FROM tag is set by the `UBUNTU_VERSION` build-arg, and the Qt package set + is picked from the image's `VERSION_ID` at build time; +- the base image is the `full` stage (`--target full`); +- each add-on is a further stage (e.g. `--target cmake-4`). + +Each image's `context`, `dockerfile`, `target`, `build_args` and `addons` +(add-on stage names) are declared in [.ci/matrix.yml][matrix-yml]. + +To add a new image, create or extend the OS's `/Dockerfile` and list it in +[.ci/matrix.yml][matrix-yml] (a new version of an existing OS needs only a +matrix entry). + +### Image naming & tags + +One image repository per registry; OS, version and variant are encoded in the +**tag**: + +| Tag | Mutable? | Meaning | +| --- | --- | --- | +| `ubuntu-24.04` | moving | Latest base image for Ubuntu 24.04 | +| `ubuntu-24.04-2.1.0` | immutable | Pinned base, `2.1.0` = this repo's semver | +| `ubuntu-24.04-cmake-4` | moving | Latest CMake 4 add-on on the 24.04 base | +| `ubuntu-24.04-cmake-4-2.1.0` | immutable | Pinned add-on | +| `arch-rolling-2026.06.01` | immutable | Date-stamped snapshot for the rolling distro | + +The repo's own semver (`MAJOR.MINOR.PATCH`) versions the **recipe**, not +OpenModelica. Day-to-day CI uses the **moving** tag; when an OpenModelica +release needs a frozen environment it pins the **immutable** tag. + +### Currently provided images + +| OS / version | Base tag | Add-ons | Status | Source | +| --- | --- | --- | --- | --- | +| Ubuntu 26.04 | `ubuntu-26.04` | – | implemented | [ubuntu/Dockerfile][ubuntu-dockerfile] | +| Ubuntu 24.04 (Noble) | `ubuntu-24.04` | `cmake-4` | implemented | [ubuntu/Dockerfile][ubuntu-dockerfile] | +| Ubuntu 22.04 (Jammy) | `ubuntu-22.04` | – | implemented | [ubuntu/Dockerfile][ubuntu-dockerfile] | +| Debian 13 (Trixie) | `debian-13` | – | placeholder | [debian/Dockerfile][debian-dockerfile] | +| AlmaLinux 9 | `almalinux-9` | – | placeholder | [almalinux/Dockerfile][almalinux-dockerfile] | +| Arch Linux (rolling) | `arch-rolling` | – | placeholder | [arch/Dockerfile][arch-dockerfile] | +| openSUSE Leap 16.0 | `opensuse-leap-16.0` | – | placeholder | [opensuse-leap/Dockerfile][opensuse-leap-dockerfile] | + +## Build locally + +**Base image** — for Ubuntu, pick the version with `UBUNTU_VERSION` and build +the `full` stage: + +```bash +docker build --pull --no-cache \ + --target full \ + --build-arg UBUNTU_VERSION=24.04 \ + --tag build-deps:ubuntu-24.04 \ + ubuntu + +# Debian/AlmaLinux/Arch follow the same pattern once implemented, e.g.: +# docker build --pull --target full --build-arg DEBIAN_VERSION=13 \ +# --tag build-deps:debian-13 debian +``` -### Debian based Images +**Add-on image** — build the add-on's stage with `--target`. It reuses the +base's cached layers, so it only adds the extra step: -- 12 Bookworm -- 11 Bullseye +```bash +docker build --pull \ + --target cmake-4 \ + --build-arg UBUNTU_VERSION=24.04 \ + --tag build-deps:ubuntu-24.04-cmake-4 \ + ubuntu +``` -### CentOS based Images +> The values to pass (`context`, `--file`, `--target`, `--build-arg`) for any +> image are exactly its fields in [.ci/matrix.yml][matrix-yml]. -- CentOS7 +## CI workflow -## Build +A single workflow, [build.yml][workflow-build-file], runs the whole pipeline so +that a release it creates can publish in the **same** run (a release created +with `GITHUB_TOKEN` cannot trigger a separate workflow): -```bash -export TAG=v1.26.1 -docker build --pull --no-cache --tag build-deps:$TAG . +```text +discover ─▶ build (all images, no push) + └─▶ release (tag only) ─▶ publish-ghcr + publish-nexus ``` -## Upload +- **build** — on every push/PR to `main` (and as the gate before release), + builds every base + add-on declared in `.ci/matrix.yml` (no push). +- **release** — on an image release tag, creates/updates the GitHub Release. +- **publish-ghcr / publish-nexus** — build, push (and on GHCR **sign**) the + tagged image (base + add-ons) to GHCR and Nexus. Also runnable via + `workflow_dispatch` with a `tag` input to re-publish without a new tag. + +## Releasing a new image version -The [publish.yml](./.github/workflows/publish.yml) workflow will build, sign and -upload the Docker image to -[GitHub Container registry](https://github.com/OpenModelica/openmodelica-build-deps/pkgs/container/build-deps) -for each release. -The [publish-nexus](./.github/workflows/publish-nexus.yml) workflow will build and upload the Docker image to [docker.openmodelica.org](https://nexus.openmodelica.org/#browse/browse:openmodelica:v2%2Fbuild-deps) +See **[RELEASING.md][releasing-md]** for the step-by-step process. ## License The original Dockerfile was taken from -[OpenModelica/OpenModelicaBuildScripts](https://github.com/OpenModelica/OpenModelicaBuildScripts). -See [LICENSE.md](./LICENSE.md). +[OpenModelica/OpenModelicaBuildScripts][build-scripts]. +See [LICENSE.md][license-md]. + +[badge-build-img]: https://github.com/OpenModelica/build-deps/actions/workflows/build.yml/badge.svg?branch=main +[workflow-build]: https://github.com/OpenModelica/build-deps/actions/workflows/build.yml +[openmodelica]: https://github.com/OpenModelica/OpenModelica +[jenkins]: https://test.openmodelica.org/jenkins/ +[matrix-yml]: ./.ci/matrix.yml +[ubuntu-dockerfile]: ./ubuntu/Dockerfile +[debian-dockerfile]: ./debian/Dockerfile +[almalinux-dockerfile]: ./almalinux/Dockerfile +[arch-dockerfile]: ./arch/Dockerfile +[opensuse-leap-dockerfile]: ./opensuse-leap/Dockerfile +[workflow-build-file]: ./.github/workflows/build.yml +[releasing-md]: ./RELEASING.md +[build-scripts]: https://github.com/OpenModelica/OpenModelicaBuildScripts +[license-md]: ./LICENSE.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..7fdf7f2 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,135 @@ +# Releasing a new image version + +A "release" publishes **one image** — a base image and *all* of its add-ons — +under both a moving and an immutable tag, to GHCR and Nexus. Releases are +driven entirely by **git tags**; you never push images by hand. + +> TL;DR — merge your change to `main`, then push a tag +> `--` (e.g. `ubuntu-24.04-2.1.0`). CI does the rest. + +## The tag grammar + +```text +-- +``` + +| Part | Example | Notes | +| --- | --- | --- | +| `` | `ubuntu` | A directory at the repo root holding `/Dockerfile`. | +| `` | `24.04`, `13`, `rolling` | Selects the version (a build-arg) within that OS. | +| `` | `2.1.0` | **This repository's** version (the recipe), independent of OpenModelica. `MAJOR.MINOR.PATCH`. | + +The pair `-` must match an entry in +[.ci/matrix.yml][ci-matrix] (you can list valid prefixes with +`python .ci/matrix.py all`). + +### What gets published + +For tag `ubuntu-24.04-2.1.0` the publish workflows build and push: + +| Image | Moving tag | Immutable tag | +| --- | --- | --- | +| base | `ubuntu-24.04` | `ubuntu-24.04-2.1.0` | +| add-on `cmake-4` | `ubuntu-24.04-cmake-4` | `ubuntu-24.04-cmake-4-2.1.0` | + +to both `ghcr.io/openmodelica/build-deps` (signed with cosign) and +`docker.openmodelica.org/build-deps`. Add-ons are extra stages that build +`FROM` the base stage in the same Dockerfile, so a release is internally +consistent and shared layers come from the build cache. + +## Choosing the next semver + +Bump relative to the last tag **for that image** (`git tag --list '--*'`): + +- **PATCH** (`2.1.0 → 2.1.1`) — rebuild for upstream package updates / security + fixes, no intended behavior change. +- **MINOR** (`2.1.0 → 2.2.0`) — added a tool or an add-on, backward compatible. +- **MAJOR** (`2.1.0 → 3.0.0`) — removed/renamed something consumers rely on, or + a base OS bump that changes the toolchain. + +Each image has its **own** semver line; bumping `ubuntu-24.04` does not affect +`debian-13`. + +## Step by step + +1. **Edit the Dockerfile** for the image: each OS has a single multi-stage + `/Dockerfile` (e.g. all Ubuntu versions share `ubuntu/Dockerfile`) where + the base is the `full` stage and add-ons are extra stages such as `cmake-4`. + Its `context`/`dockerfile`/`target`/`build_args` are in + [.ci/matrix.yml][ci-matrix]. + +2. **Open a PR to `main`.** [build.yml][build-yml] builds + every image (no push). Confirm your image builds. Build it locally too — + reuse the `context` / `dockerfile` / `target` / `build_args` from + `.ci/matrix.yml`: + + ```bash + # Ubuntu base (the `full` stage): + docker build --pull --target full --build-arg UBUNTU_VERSION=24.04 \ + --tag build-deps:ubuntu-24.04 ubuntu + # each add-on (its own --target stage, same Dockerfile): + docker build --pull --target cmake-4 --build-arg UBUNTU_VERSION=24.04 \ + --tag build-deps:ubuntu-24.04-cmake-4 ubuntu + ``` + +3. **Merge to `main`.** + +4. **Tag and push** the release from the merge commit on `main`: + + ```bash + git checkout main && git pull + git tag ubuntu-24.04-2.1.0 + git push origin ubuntu-24.04-2.1.0 + ``` + +5. **CI publishes automatically.** Pushing the tag runs the single + [build.yml](./.github/workflows/build.yml) pipeline, which in one run: + builds the images, creates/updates the GitHub Release, then builds + pushes + the tagged image (base + add-ons) to GHCR (signed) and Nexus. + + Watch the Actions tab; when green the new tags are live. + +## Re-running a publish without a new tag + +Use the **workflow_dispatch** trigger on +[build.yml](./.github/workflows/build.yml) and pass the existing tag (e.g. +`ubuntu-24.04-2.1.0`). The `publish-ghcr` / `publish-nexus` jobs rebuild and +re-push the same tags (the release step is skipped) — handy after a transient +failure. + +## Adding a brand-new image + +1. For a **new OS**, create the single multi-stage `/Dockerfile` (see + `ubuntu/Dockerfile`). For a **new version of an existing OS**, no new + Dockerfile is needed — just add a matrix entry: e.g. a new Ubuntu version + adds `dockerfile: ubuntu/Dockerfile`, `target: full` and + `build_args: { UBUNTU_VERSION: "" }`. +2. Add the image to [.ci/matrix.yml](./.ci/matrix.yml). +3. PR → merge → release as above with `--1.0.0`. + +## Adding an add-on + +Add-ons are extra build **stages** in the image's Dockerfile that build `FROM` +the base stage and add exactly one thing: + +```dockerfile +FROM full AS my-addon # `full` is the base stage +# ... install the one extra thing (a pinned toolchain, a source-built lib) ... +``` + +Then list the stage name under the image's `addons:` in +[.ci/matrix.yml](./.ci/matrix.yml). CI builds it with `--target my-addon` and +publishes `…-my-addon` (moving) and `…-my-addon-` (immutable). For +single-stage bases (Debian/Arch) add the `full`/base stage name and a `target:` +to the entry first, or split that Dockerfile into stages the same way. + +## Arch / rolling snapshots + +Arch has no version number. The moving tag `arch-rolling` always tracks the +latest build. For a reproducible pin, release with a date-stamped tag, +e.g. `arch-rolling-2026.6.1` — the publish workflows match a `\d+\.\d+\.\d+` +suffix, so use `YYYY.M.D` (without padding, e.g. `2026.6.1` not `2026.06.01`): +SemVer 2.0.0 forbids leading zeros in numeric identifiers. + +[ci-matrix]: ./.ci/matrix.yml +[build-yml]: ./.github/workflows/build.yml diff --git a/almalinux/Dockerfile b/almalinux/Dockerfile new file mode 100644 index 0000000..fd8ca1e --- /dev/null +++ b/almalinux/Dockerfile @@ -0,0 +1,26 @@ +# PLACEHOLDER — OpenModelica build-deps image for AlmaLinux. +# +# Not implemented yet. Use the same layout as ../ubuntu/Dockerfile: a single +# multi-stage Dockerfile covering ALL AlmaLinux versions, with these stages: +# base OpenModelica build dependencies only +# venv Python virtualenv at /opt/venv, built in isolation and copied later +# full the published base image (build-deps:almalinux-) +# optional add-on stages (FROM full) +# +# The AlmaLinux version is selected with a build-arg, e.g.: +# ARG ALMALINUX_VERSION=9 +# FROM almalinux:${ALMALINUX_VERSION} AS base +# +# Note: AlmaLinux is RPM-based (dnf) and there is no OpenModelica apt repo — +# install the build dependencies directly with dnf. +# +# To implement: +# 1. Replace this file with a real multi-stage Dockerfile (see +# ../ubuntu/Dockerfile and RELEASING.md). +# 2. Add the almalinux entries to .ci/matrix.yml +# (context: almalinux, dockerfile: almalinux/Dockerfile, target: full, +# build_args: { ALMALINUX_VERSION: "" }). +# 3. Open a PR; build.yml will build it. +# +# Until then AlmaLinux is intentionally absent from .ci/matrix.yml, so CI does +# not try to build it. diff --git a/arch/Dockerfile b/arch/Dockerfile new file mode 100644 index 0000000..7f51d9e --- /dev/null +++ b/arch/Dockerfile @@ -0,0 +1,25 @@ +# PLACEHOLDER — OpenModelica build-deps image for Arch Linux. +# +# Not implemented yet. Use the same layout as ../ubuntu/Dockerfile: a single +# multi-stage Dockerfile, with these stages: +# base OpenModelica build dependencies only +# venv Python virtualenv at /opt/venv, built in isolation and copied later +# full the published base image (build-deps:arch-rolling) +# optional add-on stages (FROM full) +# +# Arch is rolling-release, so there is no version build-arg: +# FROM archlinux:latest AS base +# +# Note: pacman-based, with no OpenModelica apt repo — install the build +# dependencies directly with pacman, and pin reproducible snapshots via a +# date-stamped immutable tag (arch-rolling-YYYY.MM.DD). +# +# To implement: +# 1. Replace this file with a real multi-stage Dockerfile (see +# ../ubuntu/Dockerfile and RELEASING.md). +# 2. Add the arch entry to .ci/matrix.yml +# (context: arch, dockerfile: arch/Dockerfile, target: full). +# 3. Open a PR; build.yml will build it. +# +# Until then Arch is intentionally absent from .ci/matrix.yml, so CI does not try +# to build it. diff --git a/debian/Dockerfile b/debian/Dockerfile new file mode 100644 index 0000000..ad9ee42 --- /dev/null +++ b/debian/Dockerfile @@ -0,0 +1,25 @@ +# PLACEHOLDER — OpenModelica build-deps image for Debian. +# +# Not implemented yet. Use the same layout as ../ubuntu/Dockerfile: a single +# multi-stage Dockerfile covering ALL Debian versions, with these stages: +# base OpenModelica build dependencies only +# venv Python virtualenv at /opt/venv, built in isolation and copied later +# full the published base image (build-deps:debian-) +# optional add-on stages (FROM full) +# +# The Debian version is selected with a build-arg, e.g.: +# ARG DEBIAN_VERSION=13 +# FROM debian:${DEBIAN_VERSION} AS base +# +# To implement: +# 1. Replace this file with a real multi-stage Dockerfile (see +# ../ubuntu/Dockerfile and RELEASING.md). A starting point exists on the +# old Debian branches / PR #36 +# (https://github.com/OpenModelica/build-deps/pull/36). +# 2. Add the debian entries to .ci/matrix.yml +# (context: debian, dockerfile: debian/Dockerfile, target: full, +# build_args: { DEBIAN_VERSION: "" }). +# 3. Open a PR; build.yml will build it. +# +# Until then Debian is intentionally absent from .ci/matrix.yml, so CI does not +# try to build it. diff --git a/opensuse-leap/Dockerfile b/opensuse-leap/Dockerfile new file mode 100644 index 0000000..ac943fc --- /dev/null +++ b/opensuse-leap/Dockerfile @@ -0,0 +1,23 @@ +# PLACEHOLDER — OpenModelica build-deps image for openSUSE Leap. +# +# Not implemented yet. Use the same layout as ../ubuntu/Dockerfile: a single +# multi-stage Dockerfile covering ALL openSUSE Leap versions, with these stages: +# base OpenModelica build dependencies only +# python Python virtualenv at /opt/venv, built in isolation and copied later +# full the published base image (build-deps:opensuse-leap-) +# optional add-on stages (FROM full) +# +# The Leap version is selected with a build-arg, e.g.: +# ARG LEAP_VERSION=15.6 +# FROM opensuse/leap:${LEAP_VERSION} AS base +# +# To implement: +# 1. Replace this file with a real multi-stage Dockerfile (see +# ../ubuntu/Dockerfile and RELEASING.md). +# 2. Uncomment the opensuse-leap entries in .ci/matrix.yml +# (context: opensuse-leap, dockerfile: opensuse-leap/Dockerfile, +# target: full, build_args: { LEAP_VERSION: "" }). +# 3. Open a PR; build.yml will build it. +# +# Until then openSUSE Leap is intentionally absent from .ci/matrix.yml, so CI +# does not try to build it. diff --git a/ubuntu/Dockerfile b/ubuntu/Dockerfile new file mode 100644 index 0000000..f7c1ba4 --- /dev/null +++ b/ubuntu/Dockerfile @@ -0,0 +1,284 @@ +# Multi-stage, shared image for all supported Ubuntu versions. +# +# The FROM tag is the only per-version difference besides a handful of Qt +# packages, so every Ubuntu image (and its add-ons) builds from this one file. +# +# Stages / build targets: +# base OpenModelica build dependencies only (apt build-dep openmodelica) +# python Python virtualenv at /opt/venv, built in isolation and copied later +# full the published BASE image: base + User's Guide toolchain + Qt + python +# cmake-4 ADD-ON: full + CMake 4 from the official Kitware release +# debug ADD-ON: full + GDB, Valgrind, and other debugging tools +# +# Build the base image (the `full` target): +# docker build --target full --build-arg UBUNTU_VERSION=26.04 \ +# --tag build-deps:ubuntu-26.04 ubuntu +# +# Build the CMake 4 add-on image (the `cmake-4` target): +# docker build --target cmake-4 --build-arg UBUNTU_VERSION=26.04 \ +# --tag build-deps:ubuntu-26.04-cmake-4 ubuntu +# +# Build the debug add-on image (the `debug` target): +# docker build --target debug --build-arg UBUNTU_VERSION=26.04 \ +# --tag build-deps:ubuntu-26.04-debug ubuntu +# +# CI passes UBUNTU_VERSION and the --target per image from .ci/matrix.yml. + +ARG UBUNTU_VERSION=26.04 + +# ── base: OpenModelica build dependencies only ──────────────────────────────── +FROM ubuntu:${UBUNTU_VERSION} AS base + +# Image / OCI metadata (inherited by the stages built FROM this one) +LABEL maintainer="AnHeuermann" \ + description="OpenModelica build-deps Docker Image" \ + organization="OpenModelica" + +LABEL org.opencontainers.image.vendor="OpenModelica" \ + org.opencontainers.image.authors="AnHeuermann" \ + org.opencontainers.image.description="OpenModelica build-deps base image (Ubuntu)" \ + org.opencontainers.image.source="https://github.com/OpenModelica/build-deps" \ + org.opencontainers.image.license="MIT" + +ENV SHELL=/bin/bash \ + LANGUAGE=en_US:en \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + +# Ensure DEBIAN_FRONTEND is only set during build +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get upgrade -qy \ + && apt-get dist-upgrade -qy \ + && apt-get install -qy \ + ca-certificates \ + curl \ + gnupg \ + lsb-release \ + && curl -fsSL https://build.openmodelica.org/apt/openmodelica.asc | gpg --dearmor -o /usr/share/keyrings/openmodelica-keyring.gpg \ + && echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openmodelica-keyring.gpg] https://build.openmodelica.org/apt \ + $(cat /etc/os-release | grep "\(UBUNTU\\|DEBIAN\\|VERSION\)_CODENAME" | sort | cut -d= -f 2 | head -1) \ + nightly" | tee /etc/apt/sources.list.d/openmodelica.list > /dev/null \ + && echo \ + "deb-src [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openmodelica-keyring.gpg] https://build.openmodelica.org/apt \ + $(cat /etc/os-release | grep "\(UBUNTU\\|DEBIAN\\|VERSION\)_CODENAME" | sort | cut -d= -f 2 | head -1) \ + nightly" | tee -a /etc/apt/sources.list.d/openmodelica.list > /dev/null \ + && apt-get update \ + && apt-get build-dep -qy openmodelica \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# ── venv: Python tooling, isolated so it caches independently ───────────────── +# Built once and copied into `full`. Uses the same python3 as `full` (same +# Ubuntu base), so the relocated /opt/venv works there unchanged. +FROM base AS venv +RUN apt-get update \ + && apt-get install -qy python3-pip python3-venv \ + && rm -rf /var/lib/apt/lists/* +RUN python3 -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +# Use permalink for doc/UsersGuide/source/requirements.txt to keep builds +# deterministic. +RUN pip install --no-cache-dir \ + junit_xml \ + lxml \ + ompython==3.6.0 \ + PyGithub \ + simplejson \ + svgwrite \ + && pip install --no-cache-dir -r \ + https://raw.githubusercontent.com/OpenModelica/OpenModelica/9c0dc9a8ab50ba652109584cb3fecaef86640b66/doc/UsersGuide/source/requirements.txt + +# ── rustdeps: Dependencies for the Rust build ───────────────── +# Built once and copied into `rust`. +FROM base AS rustdeps +RUN apt-get update \ + && apt-get install --no-install-recommends -qy python3-pip python3-venv curl \ + && rm -rf /var/lib/apt/lists/* + +# Specific versions needed for caching Rust crates +ARG WASM_BINDGEN_VERSION="0.2.125" +ARG RUST_NIGHTLY="nightly-2026-05-31" + +ENV RUSTUP_HOME=/opt/rust/rustup \ + CARGO_HOME=/opt/rust/cargo \ + XWIN_CACHE_DIR=/opt/cargo-xwin + +# Install Rust +RUN curl https://sh.rustup.rs | bash -s -- -y \ + --profile minimal \ + --default-toolchain "${RUST_NIGHTLY}" \ + --target x86_64-pc-windows-msvc \ + --target aarch64-pc-windows-msvc \ + --target wasm32-unknown-unknown && \ + . "$CARGO_HOME/env" && \ + which cargo && cargo --version && \ + rustup component add rustc-codegen-cranelift-preview clippy rustfmt && \ + cargo install wasm-bindgen-cli --version "${WASM_BINDGEN_VERSION}" && \ + cargo install sccache --locked && \ + cargo install cargo-nextest --locked && \ + cargo install wasm-opt && \ + cargo install --locked cargo-xwin && \ + cargo xwin cache xwin \ + --xwin-arch x86_64,aarch64 && \ + rm -rf "$CARGO_HOME/registry" "$CARGO_HOME/git" \ + "$CARGO_HOME/.package-cache" \ + "$HOME/.rustup/downloads" "$HOME/.rustup/tmp" \ + "${CARGO_TARGET_DIR:-/nonexistent}" && \ + mkdir -p "$CARGO_HOME/registry" && chmod ugo+rwx -R /opt/rust + +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + git python3 python3-pip python3-venv ca-certificates xz-utils flang llvm && \ + # --- Emscripten 4.0.7 (required by Qt 6.10.x; Ubuntu has 3.1.69) --- + git clone --depth 1 https://github.com/emscripten-core/emsdk.git /opt/emsdk; \ + cd /opt/emsdk && \ + ./emsdk install 4.0.7 && \ + ./emsdk activate 4.0.7 && \ + # --- Qt 6.10.2: WebAssembly (single-threaded) + matching 26.04 host --- + python3 -m pip install --no-cache-dir --break-system-packages aqtinstall && \ + aqt install-qt linux desktop 6.10.2 linux_gcc_64 -m qtwebchannel qtpositioning qtpdf qthttpserver qtquick3d qtshadertools -O /opt/Qt && \ + aqt install-qt all_os wasm 6.10.2 wasm_singlethread -m qt5compat qtquick3d qtshadertools -O /opt/Qt && \ + aqt install-qt windows desktop 6.10.2 win64_msvc2022_64 -m qtwebchannel qtpositioning qtpdf qthttpserver qtquick3d qtshadertools -O /opt/Qt && \ + rm -rf /var/lib/apt/lists/* && \ + chmod +x /opt/Qt/6.10.2/wasm_singlethread/bin/qt-cmake \ + /opt/Qt/6.10.2/wasm_singlethread/bin/qt-cmake-create \ + /opt/Qt/6.10.2/wasm_singlethread/bin/qt-configure-module + +# ── full: the published base image ──────────────────────────────────────────── +# - tools to build the User's Guide (common to all Ubuntu versions) +# - Qt5/Qt6 packages (version-specific: Jammy ships an older Qt6 that lacks +# some packages available on 24.04+, so the set is chosen from VERSION_ID) +FROM base AS full +ARG DEBIAN_FRONTEND=noninteractive + +# Package lists as build-args, so they are easy to read and to override: +# - COMMON_PKGS : tools common to all Ubuntu versions (User's Guide, …) +# - QT_PKGS_22_04 : Qt set for Jammy (older Qt6, lacks some 24.04+ packages) +# - QT_PKGS : Qt set for 24.04+ (the default) +ARG COMMON_PKGS="\ + aspell \ + bibtex2html \ + bison \ + ccache \ + clang-tools \ + devscripts \ + docker.io \ + doxygen \ + equivs \ + flex \ + git \ + gnuplot-nox \ + inkscape \ + intel-opencl-icd \ + latexmk \ + libcurl4-gnutls-dev \ + libmldbm-perl \ + locales \ + ocl-icd-opencl-dev \ + opencl-headers \ + pandoc \ + pocl-opencl-icd \ + poppler-utils \ + python3-pip \ + python3-venv \ + subversion \ + texlive-base \ + texlive-bibtex-extra \ + texlive-lang-greek \ + texlive-latex-extra \ + unzip \ + wget \ + xsltproc \ + xvfb \ + zip" +ARG QT_PKGS_22_04="\ + libqt6core5compat6-dev \ + libqt6opengl6-dev \ + libqt6openglwidgets6 \ + libqt6svg6-dev \ + qt6-base-dev \ + qt6-l10n-tools \ + qt6-tools-dev \ + qt6-tools-dev-tools \ + qt6-webengine-dev \ + qt6-webengine-dev-tools \ + qt6-quick3d-dev \ + qttools5-dev \ + qtwebengine5-dev" +ARG QT_PKGS="\ + libqt6core5compat6-dev \ + libqt6opengl6-dev \ + libqt6openglwidgets6 \ + libqt6svg6-dev \ + qt6-base-dev \ + qt6-httpserver-dev \ + qt6-scxml-dev \ + qt6-tools-dev \ + qt6-tools-dev-tools \ + qt6-webengine-dev \ + qt6-websockets-dev \ + qt6-quick3d-dev \ + qtwebengine5-dev" +RUN apt-get update \ + && . /etc/os-release \ + && case "${VERSION_ID}" in \ + 22.04) qt="${QT_PKGS_22_04}" ;; \ + *) qt="${QT_PKGS}" ;; \ + esac \ + && apt-get install -qy ${COMMON_PKGS} ${qt} \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Pull in the venv built in its own stage instead of rebuilding it here. +COPY --from=venv /opt/venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +# ── cmake-4 add-on: full + CMake 4 from the official Kitware release ────────── +# Overrides any apt-provided cmake. Built with `--target cmake-4`. +FROM full AS cmake-4 +LABEL org.opencontainers.image.description="OpenModelica build-deps (Ubuntu) with latest CMake 4" +ARG CMAKE_VERSION=4.3.4 +RUN curl -fsSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-$(uname -m).sh" \ + -o /tmp/cmake-install.sh \ + && chmod +x /tmp/cmake-install.sh \ + && /tmp/cmake-install.sh --skip-license --prefix=/usr/local \ + && rm /tmp/cmake-install.sh + +# ── debug add-on: full + GDB, Valgrind, and other debugging tools ───────────── +# Overrides nothing in full; adds debuggers and profilers on top. +# Built with `--target debug`. +FROM full AS debug +LABEL org.opencontainers.image.description="OpenModelica build-deps (Ubuntu) with debug tools" +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get install -qy \ + gdb \ + linux-tools-generic \ + valgrind \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +FROM full AS rust +LABEL org.opencontainers.image.description="OpenModelica build-deps (Ubuntu) for the Rust targets" + +ENV EMSDK=/opt/emsdk \ + QT_HOST_PATH=/opt/Qt/6.10.2/gcc_64 \ + RUSTUP_HOME=/opt/rust/rustup \ + CARGO_HOME=/opt/rust/cargo \ + XWIN_CACHE_DIR=/opt/cargo-xwin \ + PATH=/opt/emsdk:/opt/emsdk/upstream/emscripten:/opt/Qt/6.10.2/wasm_singlethread/bin:/opt/rust/cargo/bin:$PATH + +COPY --from=rustdeps /opt/emsdk /opt/emsdk +COPY --from=rustdeps /opt/Qt /opt/Qt +COPY --from=rustdeps /opt/rust /opt/rust + +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update \ + && apt-get install -qy \ + wine \ + mold \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*