From 3df7bdbc1e48cdbbbb60f364577f73080fce245e Mon Sep 17 00:00:00 2001 From: Matej Gomboc Date: Wed, 17 Jun 2026 11:26:10 +0200 Subject: [PATCH 1/2] Reformulate dev environment as a Dockerfile-based devcontainer Port the toolchain from the deprecated ARMCortexM-CppLib-DevEnv, dropping the prebuilt-image model in favour of a Dockerfile consumed directly as a devcontainer / CI container. - Dockerfile: arm-none-eabi GCC 14.3.rel1, CMake, Ninja, Task and LLVM FileCheck. Removed cosign and the GHCR publish/sign/cleanup machinery; updated labels and licence to embedded-society / GPL-3.0. - CI: ci_pr_validation.yml (hadolint + docker build + Trivy, no push) and scheduled_build.yml (weekly no-cache build to catch upstream drift). - Dev config: .dockerignore, .editorconfig, .gitattributes, .vscode, .github (CODEOWNERS, dependabot). - README rewritten around build-from-Dockerfile / devcontainer usage. - Rename LICENSE -> LICENCE for org consistency. Co-Authored-By: Claude Opus 4.8 (1M context) --- .dockerignore | 15 +++ .editorconfig | 21 +++++ .gitattributes | 3 + .github/CODEOWNERS | 1 + .github/dependabot.yml | 25 +++++ .github/workflows/ci_pr_validation.yml | 65 +++++++++++++ .github/workflows/scheduled_build.yml | 40 ++++++++ .vscode/extensions.json | 11 +++ .vscode/settings.json | 14 +++ Dockerfile | 126 +++++++++++++++++++++++++ LICENSE => LICENCE | 0 README.md | 53 ++++++++++- 12 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci_pr_validation.yml create mode 100644 .github/workflows/scheduled_build.yml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 Dockerfile rename LICENSE => LICENCE (100%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..79776e5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +# Git files +.git +.gitignore +.gitattributes + +# GitHub files +.github + +# Documentation +README.md +LICENCE + +# Editor configs +.editorconfig +.vscode diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a8e8231 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# http://editorconfig.org +# VS2022: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespace64 +# VSCode: https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig + +# top-most EditorConfig file +root = true + +# default settings: +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +# Markdown files need trailing whitespace for line breaks +[*.md] +trim_trailing_whitespace = false + +# all Shell scripts must use Linux line endings +[*.sh] +end_of_line = lf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5a6e62d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# automatically normalize line endings to the user's GIT preferences +* text=auto +*.sh text eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..2548f50 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @MatejGomboc diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4df32d9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + time: "00:00" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci" + include: "scope" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" + time: "00:00" + labels: + - "dependencies" + - "docker" + commit-message: + prefix: "docker" + include: "scope" diff --git a/.github/workflows/ci_pr_validation.yml b/.github/workflows/ci_pr_validation.yml new file mode 100644 index 0000000..641f942 --- /dev/null +++ b/.github/workflows/ci_pr_validation.yml @@ -0,0 +1,65 @@ +name: PR Validation + +on: + pull_request: + branches: ["**"] # Run PR validation on any branch. + + workflow_dispatch: # Manual trigger option. + +jobs: + verify_pr: + runs-on: ubuntu-latest + timeout-minutes: 15 + + permissions: + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Lint Dockerfile + uses: hadolint/hadolint-action@v3.3.0 + with: + dockerfile: Dockerfile + failure-threshold: error + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build Docker image (no push, just build) + id: build + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + push: false # Never push during PR validation. + load: true # Load image locally for scanning. + tags: ci-pr-validation:${{ github.event.pull_request.number || github.run_number }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: false # Disabled implicit provenance. + + - name: Run Trivy vulnerability scanner (table output) + uses: aquasecurity/trivy-action@0.34.2 + with: + image-ref: ci-pr-validation:${{ github.event.pull_request.number || github.run_number }} + format: 'table' + severity: 'CRITICAL,HIGH,MEDIUM,LOW' + exit-code: '0' # Don't fail the build on vulnerabilities, just report the findings. + + - name: Run Trivy vulnerability scanner (SARIF output) + uses: aquasecurity/trivy-action@0.34.2 + with: + image-ref: ci-pr-validation:${{ github.event.pull_request.number || github.run_number }} + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH,MEDIUM,LOW' + exit-code: '0' # Don't fail the build on vulnerabilities, just report the findings. + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v4 + if: always() + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/scheduled_build.yml b/.github/workflows/scheduled_build.yml new file mode 100644 index 0000000..3f75a86 --- /dev/null +++ b/.github/workflows/scheduled_build.yml @@ -0,0 +1,40 @@ +name: Scheduled Build + +on: + schedule: + - cron: '0 0 * * 6' # Weekly on Saturday 00:00 UTC, to catch upstream drift. + + workflow_dispatch: # Manual trigger option. + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 20 + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Lint Dockerfile + uses: hadolint/hadolint-action@v3.3.0 + with: + dockerfile: Dockerfile + failure-threshold: error + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + # No build cache: the point of this scheduled run is to actually + # re-download the pinned external artifacts (ARM toolchain, CMake, + # Ninja, Task) so we notice if an upstream URL or release disappears. + - name: Build Docker image (no cache, no push) + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + push: false + no-cache: true + provenance: false # Disabled implicit provenance. diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d025d82 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "davidanson.vscode-markdownlint", + "donjayamanne.githistory", + "editorconfig.editorconfig", + "felipecaputo.git-project-manager", + "github.vscode-github-actions", + "ms-vscode.notepadplusplus-keybindings", + "redhat.vscode-yaml" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..425da02 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "editor.insertSpaces": true, + "editor.tabSize": 4, + "editor.trimAutoWhitespace": true, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "editor.renderWhitespace": "boundary", + "[markdown]": { + "files.trimTrailingWhitespace": false + }, + "[shellscript]": { + "files.eol": "\n" + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..548538c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,126 @@ +FROM debian:13-slim AS downloader + +ARG NINJA_VERSION=v1.13.1 +ARG CMAKE_VERSION=v4.1.1 +ARG ARM_NONE_EABI_VERSION=14.3.rel1 +ARG TASK_VERSION=v3.45.5 + +# hadolint ignore=DL3002 +USER root:root + +WORKDIR /root + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# hadolint ignore=DL3008,DL4001 +RUN apt-get update && \ + apt-get install -y --no-install-recommends ca-certificates wget unzip tar xz-utils curl jq && \ + \ + NINJA_FILE="ninja-linux.zip" && \ + NINJA_API_RESPONSE=$(curl -sSL "https://api.github.com/repos/ninja-build/ninja/releases/tags/${NINJA_VERSION}") && \ + NINJA_DIGEST=$(echo "${NINJA_API_RESPONSE}" | jq -r ".assets[] | select(.name == \"${NINJA_FILE}\") | .digest") && \ + if [ -z "${NINJA_DIGEST}" ] || [ "${NINJA_DIGEST}" = "null" ]; then \ + echo "ERROR: Failed to get Ninja digest from GitHub API"; \ + echo "API Response: ${NINJA_API_RESPONSE}"; \ + exit 1; \ + fi && \ + if [[ ! "${NINJA_DIGEST}" =~ ^sha256: ]]; then \ + echo "ERROR: Unexpected Ninja digest format: ${NINJA_DIGEST}"; \ + echo "Expected format: sha256:HASH"; \ + exit 1; \ + fi && \ + NINJA_HASH=$(echo "${NINJA_DIGEST}" | cut -d: -f2) && \ + wget -q "https://github.com/ninja-build/ninja/releases/download/${NINJA_VERSION}/${NINJA_FILE}" && \ + echo "${NINJA_HASH} ${NINJA_FILE}" | sha256sum --check && \ + unzip -q "${NINJA_FILE}" -d /usr/local/bin && \ + rm "${NINJA_FILE}" && \ + \ + CMAKE_VERSION_NUM="${CMAKE_VERSION#v}" && \ + CMAKE_FILE="cmake-${CMAKE_VERSION_NUM}-linux-x86_64.tar.gz" && \ + CMAKE_API_RESPONSE=$(curl -sSL "https://api.github.com/repos/Kitware/CMake/releases/tags/${CMAKE_VERSION}") && \ + CMAKE_DIGEST=$(echo "${CMAKE_API_RESPONSE}" | jq -r ".assets[] | select(.name == \"${CMAKE_FILE}\") | .digest") && \ + if [ -z "${CMAKE_DIGEST}" ] || [ "${CMAKE_DIGEST}" = "null" ]; then \ + echo "ERROR: Failed to get CMake digest from GitHub API"; \ + echo "API Response: ${CMAKE_API_RESPONSE}"; \ + exit 1; \ + fi && \ + if [[ ! "${CMAKE_DIGEST}" =~ ^sha256: ]]; then \ + echo "ERROR: Unexpected CMake digest format: ${CMAKE_DIGEST}"; \ + echo "Expected format: sha256:HASH"; \ + exit 1; \ + fi && \ + CMAKE_HASH=$(echo "${CMAKE_DIGEST}" | cut -d: -f2) && \ + wget -q "https://github.com/Kitware/CMake/releases/download/${CMAKE_VERSION}/${CMAKE_FILE}" && \ + echo "${CMAKE_HASH} ${CMAKE_FILE}" | sha256sum --check && \ + mkdir -p /opt/cmake && \ + tar -xzf "${CMAKE_FILE}" -C /opt/cmake --strip-components=1 && \ + rm "${CMAKE_FILE}" && \ + \ + ARM_NONE_EABI_FILE="arm-gnu-toolchain-${ARM_NONE_EABI_VERSION}-x86_64-arm-none-eabi.tar.xz" && \ + ARM_NONE_EABI_HASH_FILE="${ARM_NONE_EABI_FILE}.sha256asc" && \ + wget -q "https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_NONE_EABI_VERSION}/binrel/${ARM_NONE_EABI_FILE}" && \ + wget -q "https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_NONE_EABI_VERSION}/binrel/${ARM_NONE_EABI_HASH_FILE}" && \ + sha256sum --check "${ARM_NONE_EABI_HASH_FILE}" && \ + mkdir -p /opt/arm-none-eabi-gcc && \ + tar -xf "${ARM_NONE_EABI_FILE}" -C /opt/arm-none-eabi-gcc --strip-components=1 && \ + rm "${ARM_NONE_EABI_FILE}" "${ARM_NONE_EABI_HASH_FILE}" && \ + \ + TASK_FILE="task_linux_amd64.tar.gz" && \ + TASK_API_RESPONSE=$(curl -sSL "https://api.github.com/repos/go-task/task/releases/tags/${TASK_VERSION}") && \ + TASK_DIGEST=$(echo "${TASK_API_RESPONSE}" | jq -r ".assets[] | select(.name == \"${TASK_FILE}\") | .digest") && \ + if [ -z "${TASK_DIGEST}" ] || [ "${TASK_DIGEST}" = "null" ]; then \ + echo "ERROR: Failed to get Task digest from GitHub API"; \ + echo "API Response: ${TASK_API_RESPONSE}"; \ + exit 1; \ + fi && \ + if [[ ! "${TASK_DIGEST}" =~ ^sha256: ]]; then \ + echo "ERROR: Unexpected Task digest format: ${TASK_DIGEST}"; \ + echo "Expected format: sha256:HASH"; \ + exit 1; \ + fi && \ + TASK_HASH=$(echo "${TASK_DIGEST}" | cut -d: -f2) && \ + wget -q "https://github.com/go-task/task/releases/download/${TASK_VERSION}/${TASK_FILE}" && \ + echo "${TASK_HASH} ${TASK_FILE}" | sha256sum --check && \ + tar -xzf "${TASK_FILE}" -C /usr/local/bin task && \ + rm "${TASK_FILE}" && \ + chmod +x /usr/local/bin/task + +FROM debian:13-slim + +LABEL org.opencontainers.image.title="ARM Embedded Development Environment" \ + org.opencontainers.image.description="Development environment for building and testing the Embedded Society's ARM Cortex-M libraries" \ + org.opencontainers.image.authors="The Embedded Society" \ + org.opencontainers.image.source="https://github.com/embedded-society/arm-dev-env" \ + org.opencontainers.image.vendor="https://github.com/embedded-society" \ + org.opencontainers.image.licenses="GPL-3.0" + +# hadolint ignore=DL3002 +USER root:root + +WORKDIR /root + +ENV PATH="/opt/arm-none-eabi-gcc/bin:/opt/cmake/bin:${PATH}" + +COPY --from=downloader /usr/local/bin/ninja /usr/local/bin/ +COPY --from=downloader /usr/local/bin/task /usr/local/bin/ +COPY --from=downloader /opt/cmake/ /opt/cmake/ +COPY --from=downloader /opt/arm-none-eabi-gcc/ /opt/arm-none-eabi-gcc/ + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# hadolint ignore=DL3008 +RUN apt-get update && \ + apt-get install -y --no-install-recommends ca-certificates git llvm-19-tools && \ + apt-get upgrade -y && \ + rm -rf /var/lib/apt/lists/* && \ + ln -s /usr/bin/FileCheck-19 /usr/bin/FileCheck && \ + ln -s /usr/bin/FileCheck-19 /usr/bin/filecheck && \ + ninja --version && \ + cmake --version && \ + arm-none-eabi-gcc --version && \ + git --version && \ + filecheck --version && \ + FileCheck --version && \ + task --version + +CMD ["/bin/bash"] diff --git a/LICENSE b/LICENCE similarity index 100% rename from LICENSE rename to LICENCE diff --git a/README.md b/README.md index d280c6d..2afd28d 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ -# arm-dev-env \ No newline at end of file +# arm-dev-env + +Development environment for building and testing the Embedded Society's ARM Cortex-M libraries. + +It is a single [`Dockerfile`](Dockerfile) bundling the cross-compilation toolchain and test tooling. Instead of pulling a prebuilt image, consumers build it on demand — as a VS Code / IDE dev container, as a CI container, or as a plain Docker image. + +## Use as a dev container + +Reference the `Dockerfile` from a consuming repository's `.devcontainer/devcontainer.json`. When this repo is added there as a git submodule named `arm-dev-env`: + +```json +{ + "name": "arm-dev-env", + "build": { + "dockerfile": "../arm-dev-env/Dockerfile", + "context": "../arm-dev-env" + }, + "initializeCommand": "git submodule update --init --recursive" +} +``` + +## Build and run manually + +Build the image: + +```bash +docker build -t arm-dev-env . +``` + +Open an interactive shell with the current directory mounted: + +```bash +docker run -it --rm -v "$(pwd)":/workspace -w /workspace arm-dev-env /bin/bash +``` + +## Included tools + +- **Build system** — CMake, Ninja, [Task](https://taskfile.dev) +- **ARM toolchain** — the complete `arm-none-eabi-*` GNU bare-metal suite +- **Testing** — LLVM FileCheck (available as both `FileCheck` and `filecheck`) +- **Version control** — Git + +## Licence + +Copyright (C) 2026 The Embedded Society . + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +See also the attached [LICENCE](LICENCE) file. From 9a1ca1ccce191c587478e1e6e971a572c8945f3b Mon Sep 17 00:00:00 2001 From: Matej Gomboc Date: Wed, 17 Jun 2026 11:37:27 +0200 Subject: [PATCH 2/2] Pin GitHub Actions to full commit SHAs Required by the embedded-society org Actions policy ("Require actions to be pinned to a full-length commit SHA"). Each action now references a 40-char SHA with a version comment for Dependabot. Also corrects the Trivy action pin: 0.34.2 was never a valid tag (trivy-action tags are v-prefixed), updated to v0.36.0. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci_pr_validation.yml | 14 +++++++------- .github/workflows/scheduled_build.yml | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci_pr_validation.yml b/.github/workflows/ci_pr_validation.yml index 641f942..eeb1299 100644 --- a/.github/workflows/ci_pr_validation.yml +++ b/.github/workflows/ci_pr_validation.yml @@ -17,20 +17,20 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Lint Dockerfile - uses: hadolint/hadolint-action@v3.3.0 + uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 with: dockerfile: Dockerfile failure-threshold: error - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 - name: Build Docker image (no push, just build) id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . platforms: linux/amd64 @@ -42,7 +42,7 @@ jobs: provenance: false # Disabled implicit provenance. - name: Run Trivy vulnerability scanner (table output) - uses: aquasecurity/trivy-action@0.34.2 + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: ci-pr-validation:${{ github.event.pull_request.number || github.run_number }} format: 'table' @@ -50,7 +50,7 @@ jobs: exit-code: '0' # Don't fail the build on vulnerabilities, just report the findings. - name: Run Trivy vulnerability scanner (SARIF output) - uses: aquasecurity/trivy-action@0.34.2 + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: ci-pr-validation:${{ github.event.pull_request.number || github.run_number }} format: 'sarif' @@ -59,7 +59,7 @@ jobs: exit-code: '0' # Don't fail the build on vulnerabilities, just report the findings. - name: Upload Trivy results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 if: always() with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/scheduled_build.yml b/.github/workflows/scheduled_build.yml index 3f75a86..60fb276 100644 --- a/.github/workflows/scheduled_build.yml +++ b/.github/workflows/scheduled_build.yml @@ -16,22 +16,22 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Lint Dockerfile - uses: hadolint/hadolint-action@v3.3.0 + uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 with: dockerfile: Dockerfile failure-threshold: error - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 # No build cache: the point of this scheduled run is to actually # re-download the pinned external artifacts (ARM toolchain, CMake, # Ninja, Task) so we notice if an upstream URL or release disappears. - name: Build Docker image (no cache, no push) - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . platforms: linux/amd64