diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3412e37 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.DS_Store +*.o +.so +*.dylib +build + +.vscode/* +!.vscode/c_cpp_properties.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json diff --git a/.github/workflows/autotag.yaml b/.github/workflows/autotag.yaml new file mode 100644 index 0000000..38d67d1 --- /dev/null +++ b/.github/workflows/autotag.yaml @@ -0,0 +1,184 @@ +name: Auto Tag + +permissions: + contents: write + pull-requests: read + +on: + pull_request: + types: [closed] + + workflow_dispatch: + inputs: + pullNumber: + description: Pull request number (i.e. 1) + required: true + dryRun: + description: Dryrun + default: "true" + type: choice + options: + - "true" + - "false" + +jobs: + check: + runs-on: ubuntu-latest + if: (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)) + outputs: + PULL_SHA: ${{ steps.check-pr.outputs.PULL_SHA }} + RELEASE_TYPE: ${{ steps.check-pr.outputs.RELEASE_TYPE }} + NEXT_TAG: ${{ steps.next-tag.outputs.NEXT_TAG }} + steps: + - name: Check pull request + id: check-pr + uses: actions/github-script@v7 + with: + retries: 3 + script: | + console.log('github.event_name', '${{ github.event_name }}') + let pullNumber + if('${{ github.event_name }}' == 'workflow_dispatch') { + pullNumber = '${{ github.event.inputs.pullNumber }}' + } else if('${{ github.event_name }}' == 'pull_request' && '${{ github.event.pull_request.merged }}' == 'true') { + pullNumber = '${{ github.event.pull_request.number }}' + } else { + console.log('no pull number found') + return + } + console.log('pullNumber', pullNumber) + + const prRes = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pullNumber + }) + if(prRes.status != 200) { + console.log('prRes', JSON.stringify(prRes)) + core.setFailed(`failed to retrieve pull request: ${prRes.status}`) + return + } + + const merged = (prRes.data != undefined && prRes.data.merged != undefined) ? prRes.data.merged : false + if('${{ github.event_name }}' == 'workflow_dispatch' && !merged) { + core.setFailed(`pull request #${pullNumber} is not merged (only merged PRs are allowed)`) + return + } + + const labels = (prRes.data != undefined && prRes.data.labels != undefined) ? prRes.data.labels : [] + let releaseType + if(labels.find(label => label.name == "release:major") != undefined) { + releaseType = 'major' + core.setOutput('RELEASE_TYPE', 'major') + } else if(labels.find(label => label.name == "release:minor") != undefined) { + releaseType = 'minor' + core.setOutput('RELEASE_TYPE', 'minor') + } else if(labels.find(label => label.name == "release:patch") != undefined) { + releaseType = 'patch' + } else { + console.log('pull request does not have release labels') + return + } + console.log('RELEASE_TYPE', releaseType) + core.setOutput('RELEASE_TYPE', releaseType) + + const pullSHA = (prRes.data != undefined && prRes.data.head != undefined && prRes.data.head.sha != undefined) ? prRes.data.head.sha : '' + console.log('PULL_SHA', pullSHA) + core.setOutput('PULL_SHA', pullSHA) + + - name: Checkout code + uses: actions/checkout@v4 + if: ${{ steps.check-pr.outputs.RELEASE_TYPE != '' }} + with: + fetch-depth: 0 # Ref: https://github.com/actions/checkout/issues/100 + + - name: Get current tag + id: current-tag + uses: "WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce" # v1.4.0 - latest as of 2025-04-27 + if: ${{ steps.check-pr.outputs.RELEASE_TYPE != '' }} + with: + fallback: v0.0.0 + + - name: Determine next semver version + id: next-semver-version + uses: madhead/semver-utils@36d1e0ed361bd7b4b77665de8093092eaeabe6ba # v4.3.0 - latest as of 2025-04-27 + if: ${{ steps.check-pr.outputs.RELEASE_TYPE != '' }} + with: + version: ${{ steps.current-tag.outputs.tag }} + + - name: Determine next tag + id: next-tag + uses: actions/github-script@v7 + if: ${{ steps.check-pr.outputs.RELEASE_TYPE != '' }} + with: + script: | + console.log('semver release: ${{ steps.next-semver-version.outputs.release }}') + console.log('semver major: ${{ steps.next-semver-version.outputs.major }}') + console.log('semver minor: ${{ steps.next-semver-version.outputs.minor }}') + console.log('semver patch: ${{ steps.next-semver-version.outputs.patch }}') + console.log('semver inc-major: ${{ steps.next-semver-version.outputs.inc-major }}') + console.log('semver inc-minor: ${{ steps.next-semver-version.outputs.inc-minor }}') + console.log('semver inc-patch: ${{ steps.next-semver-version.outputs.inc-patch }}') + + let nextTag + if('${{ steps.check-pr.outputs.RELEASE_TYPE }}' == 'major') { + nextTag = '${{ steps.next-semver-version.outputs.inc-major }}' + } else if('${{ steps.check-pr.outputs.RELEASE_TYPE }}' == 'minor') { + nextTag = '${{ steps.next-semver-version.outputs.inc-minor }}' + } else if('${{ steps.check-pr.outputs.RELEASE_TYPE }}' == 'patch') { + nextTag = '${{ steps.next-semver-version.outputs.inc-patch }}' + } else { + core.setFailed('invalid RELEASE_TYPE: ${{ steps.check-pr.outputs.RELEASE_TYPE }}') + return + } + console.log('nextTag', nextTag) + core.setOutput('NEXT_TAG', nextTag) + + tag: + runs-on: ubuntu-latest + needs: check + if: ${{ needs.check.outputs.NEXT_TAG != '' }} + steps: + - name: Create tag + id: create-tag + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PAT_WORKFLOW }} + retries: 3 + script: | + if('${{ github.event.inputs.dryRun }}' == 'true') { + console.log('skipping tag creation due to dryrun') + return + } + const createTagRes = await github.rest.git.createTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: 'v${{ needs.check.outputs.NEXT_TAG }}', + message: 'v${{ needs.check.outputs.NEXT_TAG }}', + object: '${{ needs.check.outputs.PULL_SHA }}', + type: 'commit', + tagger: { + name: '${{ secrets.GIT_USER_NAME }}', + email: '${{ secrets.GIT_USER_EMAIL }}' + } + }) + if(createTagRes.status != 201) { + console.log('createTagRes', JSON.stringify(createTagRes)) + core.setFailed(`failed to create tag: ${createTagRes.status}`) + return + } + console.log('new tag', createTagRes.data.tag) + console.log('new tag sha', createTagRes.data.sha) + + const createRefRes = await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${createTagRes.data.tag}`, + sha: createTagRes.data.sha + }) + if(createRefRes.status != 201) { + console.log('createRefRes', JSON.stringify(createRefRes)) + core.setFailed(`failed to create reference: ${createRefRes.status}`) + return + } + console.log('new tag ref', createRefRes.data.ref) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..3756291 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,69 @@ +name: Release + +permissions: + contents: write + +on: + push: + tags: ["v*.*.*"] + + workflow_dispatch: + inputs: + releaseTag: + description: Existing git tag (i.e. v0.1.0) + required: true + dryRun: + description: Dryrun + default: "true" + type: choice + options: + - "true" + - "false" + +jobs: + check: + runs-on: ubuntu-latest + if: (github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref_type == 'tag')) + outputs: + RELEASE_TAG: ${{ steps.check-tag.outputs.RELEASE_TAG }} + REPO_NAME: ${{ steps.check-tag.outputs.REPO_NAME }} + steps: + - name: Check release tag + id: check-tag + uses: actions/github-script@v7 + with: + script: | + console.log('github.event_name', '${{ github.event_name }}') + console.log('github.ref_type', '${{ github.ref_type }}') + let releaseTag + if('${{ github.event_name }}' == 'workflow_dispatch') { + releaseTag = '${{ github.event.inputs.releaseTag }}' + } else if('${{ github.event_name }}' == 'push' && '${{ github.ref_type }}' == 'tag') { + releaseTag = '${{ github.ref }}'.replace(/^refs\/tags\//, ''); + } else { + console.log('no semver tag found') + return + } + console.log('RELEASE_TAG', releaseTag) + core.setOutput('RELEASE_TAG', releaseTag) + console.log('REPO_NAME', context.repo.repo) + core.setOutput('REPO_NAME', context.repo.repo) + + - name: Check tag semver + id: check-tag-semver + uses: madhead/semver-utils@36d1e0ed361bd7b4b77665de8093092eaeabe6ba # v4.3.0 - latest as of 2025-04-27 + if: ${{ steps.check-tag.outputs.RELEASE_TAG != '' }} + with: + version: ${{ steps.check-tag.outputs.RELEASE_TAG }} + lenient: false # fail on error + + release: + runs-on: ubuntu-latest + needs: check + if: ${{ needs.check.outputs.RELEASE_TAG != '' }} + steps: + - name: Make a release + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 - latest as of 2025-04-27 + if: github.event.inputs.dryRun != 'true' + with: + tag_name: ${{ needs.check.outputs.RELEASE_TAG }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..9b55164 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,127 @@ +name: Test + +on: + push: + branches: + - main + tags: + - '*' + pull_request: + branches: + - '*' + + workflow_dispatch: + inputs: + logLevel: + description: Log Level + default: info + type: choice + options: + - debug + - error + - fatal + - info + - panic + - warning + environment: + description: Environment + default: test + +jobs: + + test-amd64: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - platform: linux/amd64 + dockerfile: Dockerfile.amd64 + tag: tinyclib:amd64 + test: amd64 + + steps: + - name: Docker Setup QEMU + if: matrix.platform != 'linux/amd64' + uses: docker/setup-qemu-action@v3.6.0 # v3.6.0 - latest as of 2025-04-27 + with: + platforms: all + + - name: Docker Setup Buildx + uses: docker/setup-buildx-action@v3.10.0 # v3.10.0 - latest as of 2025-04-27 + + - name: Checkout code + uses: actions/checkout@v4 # v4.2.2 - latest as of 2025-04-27 + + - name: Create Buildx + run: | + docker buildx create --name tests --use + docker buildx inspect --bootstrap + + - name: Build images + run: | + docker images + docker buildx ls --no-trunc + docker buildx build --platform ${{ matrix.platform }} -f scripts/tests/docker/${{ matrix.dockerfile }} -t ${{ matrix.tag }} . --load + docker images + + - name: Test + run: | + for platform in ${{ matrix.platform }}; do + for tag in ${{ matrix.tag }}; do + echo "Running container for platform: $platform, tag: $tag" + docker run --platform $platform $tag + done + done + + test-arm64: + runs-on: ubuntu-24.04-arm + strategy: + matrix: + include: + - platform: linux/arm64 + dockerfile: Dockerfile.arm64 + tag: tinyclib:arm64 + test: arm64 + + - platform: linux/arm/v6 + dockerfile: Dockerfile.armv6 + tag: tinyclib:armv6 + test: armv6 + + - platform: linux/arm/v7 + dockerfile: Dockerfile.armv7 + tag: tinyclib:armv7 + test: armv7 + + - platform: linux/arm64/v8 + dockerfile: Dockerfile.armv8 + tag: tinyclib:armv8 + test: armv8 + + steps: + - name: Docker Setup Buildx + uses: docker/setup-buildx-action@v3.10.0 # v3.10.0 - latest as of 2025-04-27 + + - name: Checkout code + uses: actions/checkout@v4 # v4.2.2 - latest as of 2025-04-27 + + - name: Create Buildx + run: | + docker buildx create --name tests --use + docker buildx inspect --bootstrap + + - name: Build images + run: | + docker images + docker buildx ls --no-trunc + docker buildx build --platform ${{ matrix.platform }} -f scripts/tests/docker/${{ matrix.dockerfile }} -t ${{ matrix.tag }} . --load + docker images + + - name: Test + run: | + for platform in ${{ matrix.platform }}; do + for tag in ${{ matrix.tag }}; do + echo "Running container for platform: $platform, tag: $tag" + docker run --platform $platform $tag + done + done diff --git a/.vscode/settings.json b/.vscode/settings.json index 932f3bd..b35902f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,9 +7,15 @@ "editor.defaultFormatter": "ms-vscode.cpptools" }, "cSpell.words": [ + "armv", "CMSIS", "ctest", - "tinyclib" + "Dryrun", + "eabi", + "libnewlib", + "noninteractive", + "tinyclib", + "trunc" ], "files.associations": { ".clang-format": "yaml", diff --git a/scripts/tests/docker/Dockerfile.amd64 b/scripts/tests/docker/Dockerfile.amd64 new file mode 100644 index 0000000..583eea6 --- /dev/null +++ b/scripts/tests/docker/Dockerfile.amd64 @@ -0,0 +1,26 @@ +# Example usage: +# Build: +# docker buildx build --platform linux/amd64 -f scripts/tests/docker/Dockerfile.amd64 -t test:amd64 . --load +# Run: +# docker run --rm -it --platform linux/amd64 test:amd64 + +# Base +FROM ubuntu:latest AS base +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + cmake \ + ninja-build \ + lld \ + git \ + ca-certificates + +# Build +FROM base AS build +COPY . /test +WORKDIR /test +RUN cmake --workflow --preset default + +ENTRYPOINT [] +CMD ["ctest", "--preset", "default"] diff --git a/scripts/tests/docker/Dockerfile.arm64 b/scripts/tests/docker/Dockerfile.arm64 new file mode 100644 index 0000000..cbf0640 --- /dev/null +++ b/scripts/tests/docker/Dockerfile.arm64 @@ -0,0 +1,28 @@ +# Example usage: +# Build: +# docker buildx build --platform linux/arm64 -f scripts/tests/docker/Dockerfile.arm64 -t test:arm64 . --load +# Run: +# docker run --rm -it --platform linux/arm64 test:arm64 + +# Base +FROM ubuntu:latest AS base +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + cmake \ + ninja-build \ + lld \ + git \ + ca-certificates \ + gcc-arm-none-eabi \ + libnewlib-arm-none-eabi + +# Build +FROM base AS build +COPY . /test +WORKDIR /test +RUN cmake --workflow --preset default + +ENTRYPOINT [] +CMD ["ctest", "--preset", "default"] diff --git a/scripts/tests/docker/Dockerfile.armv6 b/scripts/tests/docker/Dockerfile.armv6 new file mode 100644 index 0000000..82c52ec --- /dev/null +++ b/scripts/tests/docker/Dockerfile.armv6 @@ -0,0 +1,25 @@ +# Example usage: +# Build: +# docker buildx build --platform linux/arm/v6 -f scripts/tests/docker/Dockerfile.armv6 -t test:armv6 . --load +# Run: +# docker run --rm -it --platform linux/arm/v6 test:armv6 + +# Base +FROM arm32v6/alpine:latest AS base +RUN echo "https://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \ + apk update && apk add --no-cache \ + build-base clang cmake ninja-build lld \ + git ca-certificates \ + binutils-arm-none-eabi \ + gcc-arm-none-eabi g++-arm-none-eabi \ + newlib-arm-none-eabi && \ + ln -s /usr/lib/ninja-build/bin/ninja /usr/bin/ninja + +# Build +FROM base AS build +COPY . /test +WORKDIR /test +RUN cmake --workflow --preset default + +ENTRYPOINT [] +CMD ["ctest", "--preset", "default"] diff --git a/scripts/tests/docker/Dockerfile.armv7 b/scripts/tests/docker/Dockerfile.armv7 new file mode 100644 index 0000000..a15d71d --- /dev/null +++ b/scripts/tests/docker/Dockerfile.armv7 @@ -0,0 +1,28 @@ +# Example usage: +# Build: +# docker buildx build --platform linux/arm/v7 -f scripts/tests/docker/Dockerfile.armv7 -t test:armv7 . --load +# Run: +# docker run --rm -it --platform linux/arm/v7 test:armv7 + +# Base +FROM arm32v7/ubuntu:latest AS base +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + cmake \ + ninja-build \ + lld \ + git \ + ca-certificates \ + gcc-arm-none-eabi \ + libnewlib-arm-none-eabi + +# Build +FROM base AS build +COPY . /test +WORKDIR /test +RUN cmake --workflow --preset default + +ENTRYPOINT [] +CMD ["ctest", "--preset", "default"] diff --git a/scripts/tests/docker/Dockerfile.armv8 b/scripts/tests/docker/Dockerfile.armv8 new file mode 100644 index 0000000..b6eabda --- /dev/null +++ b/scripts/tests/docker/Dockerfile.armv8 @@ -0,0 +1,28 @@ +# Example usage: +# Build: +# docker buildx build --platform linux/arm64/v8 -f scripts/tests/docker/Dockerfile.armv8 -t test:armv8 . --load +# Run: +# docker run --rm -it --platform linux/arm64/v8 test:armv8 + +# Base +FROM arm64v8/ubuntu:latest AS base +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + cmake \ + ninja-build \ + lld \ + git \ + ca-certificates \ + gcc-arm-none-eabi \ + libnewlib-arm-none-eabi + +# Build +FROM base AS build +COPY . /test +WORKDIR /test +RUN cmake --workflow --preset default + +ENTRYPOINT [] +CMD ["ctest", "--preset", "default"]