From a574f39e09436ff49fb5be318e59eb62b9c7cdbe Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Mon, 16 Feb 2026 23:56:27 +0100 Subject: [PATCH 01/13] ci: Add clang-format check + separate rustfmt --- .github/workflows/ci.yml | 3 --- .github/workflows/formatting.yml | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/formatting.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f438eb44..825a89cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,13 +25,10 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust}} - components: rustfmt - name: Build run: cargo build --all-features - name: Cargo Test run: cargo test --workspace --all-features - - name: Format (fix with `cargo fmt`) - run: cargo fmt -- --check - name: Run unit-tests run: tests/run_all.sh shell: bash diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml new file mode 100644 index 00000000..f81cf639 --- /dev/null +++ b/.github/workflows/formatting.yml @@ -0,0 +1,35 @@ +name: formatting + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + rustfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: rustfmt + - name: Check Rust formatting + run: cargo fmt -- --check + + clang-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + - name: Check C/C++ formatting + uses: DoozyX/clang-format-lint-action@v0.18 + with: + source: "." + extensions: "h,hpp,c,cc,cpp,cxx" + exclude: "./crates/bender-slang/vendor" From d52ad367cf9cdbc8013d2b6228d347400789c028 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 3 Mar 2026 21:57:29 +0100 Subject: [PATCH 02/13] ci(release): Migrate to `cargo-dist` for releases --- .github/scripts/gen_dockerfile.sh | 58 ------ .github/scripts/package.sh | 39 ---- .github/workflows/release.yaml | 297 ------------------------------ .github/workflows/release.yml | 296 +++++++++++++++++++++++++++++ Cargo.toml | 4 + README.md | 16 +- dist-workspace.toml | 34 ++++ 7 files changed, 339 insertions(+), 405 deletions(-) delete mode 100755 .github/scripts/gen_dockerfile.sh delete mode 100755 .github/scripts/package.sh delete mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/release.yml create mode 100644 dist-workspace.toml diff --git a/.github/scripts/gen_dockerfile.sh b/.github/scripts/gen_dockerfile.sh deleted file mode 100755 index 512e9700..00000000 --- a/.github/scripts/gen_dockerfile.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash - -export filename="Dockerfile" -rm -f $filename -touch $filename - -if [ $(echo $full_tgtname | cut -d ':' -f 1) = "rhel" ]; then - export maj_version=$(echo $full_tgtname | cut -d ':' -f 2) - export full_tgtname=redhat/ubi$(echo $maj_version | cut -d '.' -f 1):$(echo $full_tgtname | cut -d ':' -f 2) - if [ $(echo $full_tgtname | cut -d ':' -f 2 | cut -d '.' -f 1) = '9' ]; then - if [ $(echo $full_tgtname | cut -d ':' -f 2 | cut -d '.' -f 2) = '0' ]; then - export full_tgtname=$full_tgtname.0 - fi - if [ $(echo $full_tgtname | cut -d ':' -f 2 | cut -d '.' -f 2) = '1' ]; then - export full_tgtname=$full_tgtname.0 - fi - fi -fi - -echo "FROM $full_tgtname" >> $filename -echo >> $filename -if [ $(echo $full_tgtname | cut -d ':' -f 1) = "centos" ]; then - echo "RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo" >> $filename - echo "RUN sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo" >> $filename - echo "RUN sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo" >> $filename - - echo "RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*" >> $filename - echo "RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*" >> $filename - echo 'RUN yum group install "Development Tools" -y && yum clean all' >> $filename -fi -if [ $(echo $full_tgtname | cut -d ':' -f 1) = "ubuntu" ]; then - echo 'RUN apt update && apt -y install build-essential curl' >> $filename -fi -if [ $(echo $full_tgtname | cut -d ':' -f 1) = "fedora" ]; then - echo 'RUN dnf -y update && dnf -y install @development-tools' >> $filename -fi -if [ $(echo $full_tgtname | cut -d ':' -f 1) = "debian" ]; then - echo 'RUN apt update && apt -y install build-essential curl gcc make' >> $filename -fi -if [ $(echo $full_tgtname | cut -d ':' -f 1) = "almalinux" ]; then - if [ $(echo $full_tgtname | cut -d ':' -f 2 | cut -d '.' -f 1) = '8' ]; then - echo 'RUN rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux' >> $filename - fi - echo 'RUN dnf -y update && dnf -y group install "Development Tools"' >> $filename -fi -if [[ $(echo $full_tgtname | cut -d ':' -f 1) == "redhat"* ]]; then - echo 'RUN dnf -y install gcc' >> $filename -fi -echo >> $filename -echo 'ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo' >> $filename -echo 'ENV PATH=$CARGO_HOME/bin:$PATH' >> $filename -echo >> $filename -echo 'RUN mkdir -p "$CARGO_HOME" && mkdir -p "$RUSTUP_HOME" && \' >> $filename -echo ' curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable && \' >> $filename -echo ' chmod -R a=rwX $CARGO_HOME' >> $filename -echo >> $filename -echo 'WORKDIR /source' >> $filename - diff --git a/.github/scripts/package.sh b/.github/scripts/package.sh deleted file mode 100755 index ed3ab3b9..00000000 --- a/.github/scripts/package.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -# This script expects two optional arguments: -# 1. The target architecture (e.g., x86_64, aarch64) -# 2. The target OS (e.g., linux, windows, macos) - -if [[ "$GITHUB_REF" =~ ^refs/tags/v.*$ ]]; then - pkgver="$(echo $GITHUB_REF | sed -n 's/^refs\/tags\/v//p')" -else - pkgver="$(echo $GITHUB_REF | sed -n 's/^refs\/tags\///p')" -fi - -if [ -z "$pkgver" ]; then - pkgver="latest" -fi - -if [ -z "$2" ] && [ -z "$1" ]; then # no arguments - release_dir="target/release" - tar_suffix="" - tar_prefix="-x86_64" -elif [ -n "$1" ] && [ -z "$2" ]; then # only first argument - release_dir="target/$1/release" - tar_suffix="" - tar_prefix="-$1" -elif [ -n "$2" ] && [ -n "$1" ]; then # both arguments - release_dir="target/$1/$2/release" - tar_suffix="-$2" - tar_prefix="-$1" -fi - -# WIESEP: Change amd64 to x86_64 to keep release names compatible with previous releases -if [ "$tar_prefix" == "-amd64" ]; then - tar_prefix="-x86_64" -fi - -tar -czf "bender-$pkgver$tar_prefix-linux-gnu$tar_suffix.tar.gz" \ - -C "./$release_dir" \ - --owner=0 --group=0 \ - bender diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 2df026bd..00000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,297 +0,0 @@ -name: release - -on: - release: - types: [created] - workflow_dispatch: - -jobs: - release_amd64: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - rust: - - stable - os: - - centos:7.4.1708 - - centos:7.6.1810 - - centos:7.7.1908 - - centos:7.8.2003 - - centos:7.9.2009 - - ubuntu:18.04 - - ubuntu:20.04 - - ubuntu:22.04 - - ubuntu:24.04 - - fedora:42 - - fedora:43 - - debian:11 - - debian:12 - - debian:13 - - rhel:8.6 - - rhel:8.7 - - rhel:8.8 - - rhel:8.9 - - rhel:8.10 - - rhel:9.0 - - rhel:9.1 - - rhel:9.2 - - rhel:9.3 - - rhel:9.4 - - rhel:9.5 - - rhel:9.6 - - rhel:9.7 - - rhel:10.0 - - rhel:10.1 - - almalinux:8.6 - - almalinux:8.7 - - almalinux:8.8 - - almalinux:8.9 - - almalinux:8.10 - - almalinux:9.0 - - almalinux:9.1 - - almalinux:9.2 - - almalinux:9.3 - - almalinux:9.4 - - almalinux:9.5 - - almalinux:9.6 - - almalinux:9.7 - - almalinux:10.0 - - almalinux:10.1 - platform: - - linux/amd64 - steps: - - uses: actions/checkout@v4 - - name: OS Build - run: | - export full_tgtname=${{ matrix.os }} - export tgtname=$(echo ${{ matrix.os }} | tr -d ':') - export full_platform=${{ matrix.platform }} - export platform=$(echo ${{ matrix.platform }} | awk -F'/' '{print $NF}') - .github/scripts/gen_dockerfile.sh - docker build ./ -t $tgtname-$platform --platform $full_platform - docker run \ - -t --rm \ - -v "$GITHUB_WORKSPACE:/source" \ - -v "$GITHUB_WORKSPACE/target/$platform/$tgtname:/source/target" \ - --platform $full_platform \ - $tgtname-$platform \ - cargo build --release --all-features; - shell: bash - - name: OS Create Package - run: | - export tgtname=$(echo ${{ matrix.os }} | tr -d ':') - export platform=$(echo ${{ matrix.platform }} | awk -F'/' '{print $NF}') - .github/scripts/package.sh $platform $tgtname; - shell: bash - - name: Upload Release Asset - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release_arm64: - runs-on: ubuntu-24.04-arm - strategy: - fail-fast: false - matrix: - rust: - - stable - os: - - ubuntu:18.04 - - ubuntu:20.04 - - ubuntu:22.04 - - ubuntu:24.04 - platform: - - linux/arm64 - steps: - - uses: actions/checkout@v4 - - name: OS Build - run: | - export full_tgtname=${{ matrix.os }} - export tgtname=$(echo ${{ matrix.os }} | tr -d ':') - export full_platform=${{ matrix.platform }} - export platform=$(echo ${{ matrix.platform }} | awk -F'/' '{print $NF}') - .github/scripts/gen_dockerfile.sh - docker build ./ -t $tgtname-$platform --platform $full_platform - docker run \ - -t --rm \ - -v "$GITHUB_WORKSPACE:/source" \ - -v "$GITHUB_WORKSPACE/target/$platform/$tgtname:/source/target" \ - --platform $full_platform \ - $tgtname-$platform \ - cargo build --release --all-features; - shell: bash - - name: OS Create Package - run: | - export tgtname=$(echo ${{ matrix.os }} | tr -d ':') - export platform=$(echo ${{ matrix.platform }} | awk -F'/' '{print $NF}') - .github/scripts/package.sh $platform $tgtname; - shell: bash - - name: Upload Release Asset - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release-gnu_amd64: - runs-on: ubuntu-latest - # Use container that supports old GLIBC versions and (hopefully) many linux OSs - # container: quay.io/pypa/manylinux2014_x86_64 - steps: - - uses: actions/checkout@v4 - - name: Setup Dockerfile - run: | - touch Dockerfile - echo "FROM quay.io/pypa/manylinux2014_x86_64" >> Dockerfile - echo "RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo" >> Dockerfile - echo "RUN sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo" >> Dockerfile - echo "RUN sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo" >> Dockerfile - - echo "RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*" >> Dockerfile - echo "RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*" >> Dockerfile - echo "RUN yum group install "Development Tools" -y && yum clean all" >> Dockerfile - echo 'ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo' >> Dockerfile - echo 'ENV PATH=$CARGO_HOME/bin:$PATH' >> Dockerfile - echo >> Dockerfile - echo 'RUN mkdir -p "$CARGO_HOME" && mkdir -p "$RUSTUP_HOME" && \' >> Dockerfile - echo ' curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable && \' >> Dockerfile - echo ' chmod -R a=rwX $CARGO_HOME' >> Dockerfile - echo >> Dockerfile - echo 'WORKDIR /source' >> Dockerfile - - name: OS build - run: | - docker build ./ -t manylinux-amd64 --platform linux/amd64 - docker run \ - -t --rm \ - -v "$GITHUB_WORKSPACE:/source" \ - -v "$GITHUB_WORKSPACE/target/amd64:/source/target" \ - --platform linux/amd64 \ - manylinux-amd64 \ - cargo build --release --all-features; - - name: GNU Create Package - run: .github/scripts/package.sh amd64 - shell: bash - - name: Upload Release Asset - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release-gnu_arm64: - runs-on: ubuntu-24.04-arm - # Use container that supports old GLIBC versions and (hopefully) many linux OSs - # container: quay.io/pypa/manylinux2014_aarch64 - steps: - - uses: actions/checkout@v4 - - name: Setup Dockerfile - run: | - touch Dockerfile - echo "FROM quay.io/pypa/manylinux2014_aarch64" >> Dockerfile - echo "RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo" >> Dockerfile - echo "RUN sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo" >> Dockerfile - echo "RUN sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo" >> Dockerfile - - echo "RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*" >> Dockerfile - echo "RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*" >> Dockerfile - echo "RUN yum group install "Development Tools" -y && yum clean all" >> Dockerfile - echo 'ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo' >> Dockerfile - echo 'ENV PATH=$CARGO_HOME/bin:$PATH' >> Dockerfile - echo >> Dockerfile - echo 'RUN mkdir -p "$CARGO_HOME" && mkdir -p "$RUSTUP_HOME" && \' >> Dockerfile - echo ' curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable && \' >> Dockerfile - echo ' chmod -R a=rwX $CARGO_HOME' >> Dockerfile - echo >> Dockerfile - echo 'WORKDIR /source' >> Dockerfile - - name: OS build - run: | - docker build ./ -t manylinux-arm64 --platform linux/arm64 - docker run \ - -t --rm \ - -v "$GITHUB_WORKSPACE:/source" \ - -v "$GITHUB_WORKSPACE/target/arm64:/source/target" \ - --platform linux/arm64 \ - manylinux-arm64 \ - cargo build --release --all-features; - - name: GNU Create Package - run: .github/scripts/package.sh arm64 - shell: bash - - name: Upload Release Asset - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release-macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - name: Install Rust - run: | - curl --proto '=https' --tlsv1.2 -sSf https://https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH - - name: universal2 install - run: | - rustup target add x86_64-apple-darwin - rustup target add aarch64-apple-darwin - cargo install universal2 - - name: MacOS Build - run: cargo-universal2 --release --all-features - - name: Get Artifact Name - run: | - if [[ "$GITHUB_REF" =~ ^refs/tags/v.*$ ]]; then \ - PKG_VERSION=$(echo $GITHUB_REF | sed -n 's/^refs\/tags\/v//p'); \ - else \ - PKG_VERSION=$(echo $GITHUB_REF | sed -n 's/^refs\/tags\///p'); \ - fi - ARTIFACT_PATHNAME="bender-$PKG_VERSION-universal-apple-darwin.tar.gz" - ARTIFACT_NAME=$(basename $ARTIFACT_PATHNAME) - echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV - echo "ARTIFACT_PATHNAME=${ARTIFACT_PATHNAME}" >> $GITHUB_ENV - - name: Create Package - run: | - gtar -czf $ARTIFACT_PATHNAME -C "./target/universal2-apple-darwin/release" --owner=0 --group=0 bender - - name: Upload Release Asset - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release-windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - name: Build - run: cargo build --release - - name: Get Artifact Name - shell: bash - run: | - if [[ "$GITHUB_REF" =~ ^refs/tags/v.*$ ]]; then \ - PKG_VERSION=$(echo $GITHUB_REF | sed -n 's/^refs\/tags\/v//p'); \ - else \ - PKG_VERSION=$(echo $GITHUB_REF | sed -n 's/^refs\/tags\///p'); \ - fi - ARTIFACT_PATHNAME="bender-$PKG_VERSION-x86_64-pc-windows-msvc.tar.gz" - ARTIFACT_NAME=$(basename $ARTIFACT_PATHNAME) - echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV - echo "ARTIFACT_PATHNAME=${ARTIFACT_PATHNAME}" >> $GITHUB_ENV - - name: Create Package - run: | - cp target/release/bender.exe . - & 'C:\Program Files\Git\usr\bin\tar.exe' czf $Env:ARTIFACT_PATHNAME --owner=0 --group=0 bender.exe - - name: Upload Release Asset - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..d9aa406b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,296 @@ +# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist +# +# Copyright 2022-2024, axodotdev +# SPDX-License-Identifier: MIT or Apache-2.0 +# +# CI that: +# +# * checks for a Git Tag that looks like a release +# * builds artifacts with dist (archives, installers, hashes) +# * uploads those artifacts to temporary workflow zip +# * on success, uploads the artifacts to a GitHub Release +# +# Note that the GitHub Release will be created with a generated +# title/body based on your changelogs. + +name: Release +permissions: + "contents": "write" + +# This task will run whenever you push a git tag that looks like a version +# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. +# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where +# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION +# must be a Cargo-style SemVer Version (must have at least major.minor.patch). +# +# If PACKAGE_NAME is specified, then the announcement will be for that +# package (erroring out if it doesn't have the given version or isn't dist-able). +# +# If PACKAGE_NAME isn't specified, then the announcement will be for all +# (dist-able) packages in the workspace with that version (this mode is +# intended for workspaces with only one dist-able package, or with all dist-able +# packages versioned/released in lockstep). +# +# If you push multiple tags at once, separate instances of this workflow will +# spin up, creating an independent announcement for each one. However, GitHub +# will hard limit this to 3 tags per commit, as it will assume more tags is a +# mistake. +# +# If there's a prerelease-style suffix to the version, then the release(s) +# will be marked as a prerelease. +on: + pull_request: + push: + tags: + - '**[0-9]+.[0-9]+.[0-9]+*' + +jobs: + # Run 'dist plan' (or host) to determine what tasks we need to do + plan: + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.plan.outputs.manifest }} + tag: ${{ !github.event.pull_request && github.ref_name || '' }} + tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} + publishing: ${{ !github.event.pull_request }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install dist + # we specify bash to get pipefail; it guards against the `curl` command + # failing. otherwise `sh` won't catch that `curl` returned non-0 + shell: bash + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh" + - name: Cache dist + uses: actions/upload-artifact@v6 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/dist + # sure would be cool if github gave us proper conditionals... + # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible + # functionality based on whether this is a pull_request, and whether it's from a fork. + # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* + # but also really annoying to build CI around when it needs secrets to work right.) + - id: plan + run: | + dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json + echo "dist ran successfully" + cat plan-dist-manifest.json + echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v6 + with: + name: artifacts-plan-dist-manifest + path: plan-dist-manifest.json + + # Build and packages all the platform-specific things + build-local-artifacts: + name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) + # Let the initial task tell us to not run (currently very blunt) + needs: + - plan + if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} + strategy: + fail-fast: false + # Target platforms/runners are computed by dist in create-release. + # Each member of the matrix has the following arguments: + # + # - runner: the github runner + # - dist-args: cli flags to pass to dist + # - install-dist: expression to run to install dist on the runner + # + # Typically there will be: + # - 1 "global" task that builds universal installers + # - N "local" tasks that build each platform's binaries and platform-specific installers + matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} + runs-on: ${{ matrix.runner }} + container: ${{ matrix.container && matrix.container.image || null }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json + steps: + - name: enable windows longpaths + run: | + git config --global core.longpaths true + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install Rust non-interactively if not already installed + if: ${{ matrix.container }} + run: | + if ! command -v cargo > /dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + fi + - name: Install dist + run: ${{ matrix.install_dist.run }} + # Get the dist-manifest + - name: Fetch local artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - name: Install dependencies + run: | + ${{ matrix.packages_install }} + - name: Build artifacts + run: | + # Actually do builds and make zips and whatnot + dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json + echo "dist ran successfully" + - id: cargo-dist + name: Post-build + # We force bash here just because github makes it really hard to get values up + # to "real" actions without writing to env-vars, and writing to env-vars has + # inconsistent syntax between shell and powershell. + shell: bash + run: | + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v6 + with: + name: artifacts-build-local-${{ join(matrix.targets, '_') }} + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + + # Build and package all the platform-agnostic(ish) things + build-global-artifacts: + needs: + - plan + - build-local-artifacts + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@v7 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/dist + # Get all the local artifacts for the global tasks to use (for e.g. checksums) + - name: Fetch local artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: cargo-dist + shell: bash + run: | + dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json + echo "dist ran successfully" + + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v6 + with: + name: artifacts-build-global + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + # Determines if we should publish/announce + host: + needs: + - plan + - build-local-artifacts + - build-global-artifacts + # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine) + if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.host.outputs.manifest }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@v7 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/dist + # Fetch artifacts from scratch-storage + - name: Fetch artifacts + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: host + shell: bash + run: | + dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json + echo "artifacts uploaded and released successfully" + cat dist-manifest.json + echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v6 + with: + # Overwrite the previous copy + name: artifacts-dist-manifest + path: dist-manifest.json + # Create a GitHub Release while uploading all files to it + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@v7 + with: + pattern: artifacts-* + path: artifacts + merge-multiple: true + - name: Cleanup + run: | + # Remove the granular manifests + rm -f artifacts/*-dist-manifest.json + - name: Create GitHub Release + env: + PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" + ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" + ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" + RELEASE_COMMIT: "${{ github.sha }}" + run: | + # Write and read notes from a file to avoid quoting breaking things + echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt + + gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* + + announce: + needs: + - plan + - host + # use "always() && ..." to allow us to wait for all publish jobs while + # still allowing individual publish jobs to skip themselves (for prereleases). + # "host" however must run to completion, no skipping allowed! + if: ${{ always() && needs.host.result == 'success' }} + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive diff --git a/Cargo.toml b/Cargo.toml index 08930c63..2e05e470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,10 @@ rust-version = "1.87.0" [workspace] members = ["crates/bender-slang"] +[profile.dist] +inherits = "release" +lto = "thin" + [dependencies] bender-slang = { path = "crates/bender-slang", optional = true} diff --git a/README.md b/README.md index 044f641f..8bffe203 100644 --- a/README.md +++ b/README.md @@ -46,25 +46,19 @@ Bender is built around the following core principles: ## Installation -To use Bender for a single project, the simplest is to download and use a precompiled binary. We provide binaries for all current versions of Ubuntu and CentOS, as well as generic Linux, on each release. Open a terminal and enter the following command: +The recommended way to install Bender is via the shell installer, which detects your platform and places the `bender` binary in `~/.cargo/bin` (on your `PATH`): ```sh curl --proto '=https' --tlsv1.2 https://pulp-platform.github.io/bender/init -sSf | sh ``` -The command downloads and executes a script that detects your distribution and downloads the appropriate `bender` binary of the latest release to your current directory. If you need a specific version of Bender (e.g., `0.21.0`), append ` -s -- 0.21.0` to that command. Alternatively, you can manually download a precompiled binary from [our Releases on GitHub][releases]. +To install a specific version (e.g., `0.28.0`), append ` -s -- 0.28.0` to that command. Alternatively, you can manually download a precompiled binary from [our Releases on GitHub][releases]. -If you prefer building your own binary, you need to [install Rust][rust-installation]. You can then build and install Bender for the current user with the following command: +To build from source, [install Rust][rust-installation] and run: ```sh cargo install bender ``` -If you need a specific version of Bender (e.g., `0.21.0`), append ` --version 0.21.0` to that command. +To enable optional features (including the Slang-backed `pickle` command), add `--all-features`. This may increase build time and require additional build dependencies. -To enable optional features (including the Slang-backed `pickle` command), install with: -```sh -cargo install bender --all-features -``` -This may increase build time and additional build dependencies. - -To install Bender system-wide, you can simply copy the binary you have obtained from one of the above methods to one of the system directories on your `PATH`. Even better, some Linux distributions have Bender in their repositories. We are currently aware of: +Some Linux distributions also have Bender in their repositories: ### [ArchLinux ![aur-shield](https://img.shields.io/aur/version/bender)][aur-bender] diff --git a/dist-workspace.toml b/dist-workspace.toml new file mode 100644 index 00000000..3e55bb0a --- /dev/null +++ b/dist-workspace.toml @@ -0,0 +1,34 @@ +[workspace] +members = ["cargo:."] + +# Config for 'dist' +[dist] +# The preferred dist version to use in CI (Cargo.toml SemVer syntax) +cargo-dist-version = "0.31.0" +# CI backends to support +ci = "github" +# The installers to generate for each app +installers = ["shell", "powershell"] +# Target platforms to build apps for (Rust target-triple syntax) +targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] +# Path that installers should place binaries in +install-path = "CARGO_HOME" +# Whether to install an updater program +install-updater = false +# Whether to pass --all-features to cargo build +all-features = true +# Whether +crt-static should be used on msvc +msvc-crt-static = false + +[dist.github-custom-runners] +# Use a slightly newer macOS runner for aarch64, +# since the default one (macos-14) uses Clang/LLVM 15.0.0. +# slang requires at least Clang/LLVM 17.0.0, which is available on macos-15. +aarch64-apple-darwin = "macos-15" +# Make sure that the minimum supported glibc version is 2.28 on Linux. +[dist.github-custom-runners.x86_64-unknown-linux-gnu] +runner = "ubuntu-22.04" +container = "quay.io/pypa/manylinux_2_28_x86_64" +[dist.github-custom-runners.aarch64-unknown-linux-gnu] +runner = "ubuntu-22.04-arm" +container = "quay.io/pypa/manylinux_2_28_aarch64" From 74fc23a7ad3c599468bd8eaf9e1554a5cb3a3b63 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 17 Feb 2026 09:05:02 +0100 Subject: [PATCH 03/13] ci: Add release build jobs --- .github/workflows/release-build.yml | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/release-build.yml diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml new file mode 100644 index 00000000..b3c93240 --- /dev/null +++ b/.github/workflows/release-build.yml @@ -0,0 +1,59 @@ +name: release-build + +# Verify that a release build succeeds on all platforms that cargo-dist will +# target. Runs on every push to master (and can be triggered manually) so +# that compatibility issues or platform-specific build failures are +# caught before tagging a release. +on: + push: + branches: [master] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + release-build: + name: Release Build (${{ matrix.target }}) + runs-on: ${{ matrix.runner }} + container: ${{ matrix.container || '' }} + strategy: + fail-fast: false + # Mirror the release matrix from dist-workspace.toml exactly, including + # the manylinux containers, so CI catches compatibility issues early. + matrix: + include: + - target: x86_64-unknown-linux-gnu + runner: ubuntu-22.04 + container: quay.io/pypa/manylinux_2_28_x86_64 + - target: aarch64-unknown-linux-gnu + runner: ubuntu-22.04-arm + container: quay.io/pypa/manylinux_2_28_aarch64 + - target: x86_64-apple-darwin + runner: macos-latest + - target: aarch64-apple-darwin + runner: macos-15 + - target: x86_64-pc-windows-msvc + runner: windows-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + - name: Install Rust non-interactively if not already installed + if: ${{ matrix.container }} + run: | + if ! command -v cargo > /dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + fi + - uses: dtolnay/rust-toolchain@stable + if: ${{ !matrix.container }} + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + with: + shared-key: release-build-${{ matrix.target }} + cache-workspace-crates: "true" + - name: Build (release) + run: cargo build --release --all-features From 8405b2d4f4a905cb58c5e0ab420ba5ae35d4975a Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 17 Feb 2026 14:51:27 +0100 Subject: [PATCH 04/13] ci: Cache rust builds --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 825a89cb..73629ed4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,10 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust}} + - uses: Swatinem/rust-cache@v2 + with: + shared-key: ci-test-${{ runner.os }}-${{ matrix.rust }} + cache-workspace-crates: "true" - name: Build run: cargo build --all-features - name: Cargo Test @@ -42,6 +46,10 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: toolchain: stable + - uses: Swatinem/rust-cache@v2 + with: + shared-key: ci-test-windows-${{ runner.os }}-stable + cache-workspace-crates: "true" - name: Build run: cargo build --all-features - name: Cargo Test @@ -59,6 +67,10 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: toolchain: stable + - uses: Swatinem/rust-cache@v2 + with: + shared-key: ci-test-macos-${{ runner.os }}-stable + cache-workspace-crates: "true" - name: Build run: cargo build --all-features - name: Cargo Test @@ -78,6 +90,10 @@ jobs: with: toolchain: stable components: clippy + - uses: Swatinem/rust-cache@v2 + with: + shared-key: ci-clippy-${{ runner.os }}-stable + cache-workspace-crates: "true" - run: cargo clippy --all-features unused-deps: @@ -88,6 +104,10 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: toolchain: stable + - uses: Swatinem/rust-cache@v2 + with: + shared-key: ci-unused-deps-${{ runner.os }}-stable + cache-workspace-crates: "true" - name: Install machete run: cargo install cargo-machete - name: Check for unused dependencies From 7fbc20e1f85daaaf23483253c395ade5da17137a Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 17 Feb 2026 15:23:29 +0100 Subject: [PATCH 05/13] ci: Cancel ongoing workflows --- .github/workflows/ci.yml | 4 ++++ .github/workflows/cli_regression.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73629ed4..37b25c20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: pull_request: workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/cli_regression.yml b/.github/workflows/cli_regression.yml index bfdcf9bd..71b5780a 100644 --- a/.github/workflows/cli_regression.yml +++ b/.github/workflows/cli_regression.yml @@ -4,6 +4,10 @@ on: pull_request: workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest From cb7b34cd4d5df2dc2dea37367d4da72d98cec1a1 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 3 Mar 2026 22:46:55 +0100 Subject: [PATCH 06/13] ci: Merge os runs --- .github/workflows/ci.yml | 64 ++++++++-------------------------------- 1 file changed, 13 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37b25c20..257e4132 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,68 +12,30 @@ concurrency: jobs: test: - runs-on: ubuntu-latest + name: Test (${{ matrix.os }}, Rust ${{ matrix.rust }}) + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.rust == 'nightly' }} strategy: fail-fast: false matrix: - rust: - - stable - - beta - - nightly - - 1.87.0 # minimum supported version - continue-on-error: ${{ matrix.rust == 'nightly' }} - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust}} - - uses: Swatinem/rust-cache@v2 - with: - shared-key: ci-test-${{ runner.os }}-${{ matrix.rust }} - cache-workspace-crates: "true" - - name: Build - run: cargo build --all-features - - name: Cargo Test - run: cargo test --workspace --all-features - - name: Run unit-tests - run: tests/run_all.sh - shell: bash - - test-windows: - runs-on: windows-latest + # Linux runs the full Rust version matrix; Windows and macOS test stable only. + os: [ubuntu-latest] + rust: [stable, beta, nightly, "1.87.0"] + include: + - os: windows-latest + rust: stable + - os: macos-latest + rust: stable steps: - uses: actions/checkout@v6 with: submodules: recursive - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - - uses: Swatinem/rust-cache@v2 - with: - shared-key: ci-test-windows-${{ runner.os }}-stable - cache-workspace-crates: "true" - - name: Build - run: cargo build --all-features - - name: Cargo Test - run: cargo test --workspace --all-features - - name: Run unit-tests - run: tests/run_all.sh - shell: bash - - test-macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable + toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 with: - shared-key: ci-test-macos-${{ runner.os }}-stable + shared-key: ci-test-${{ matrix.os }}-${{ matrix.rust }} cache-workspace-crates: "true" - name: Build run: cargo build --all-features From ce7c5a6ac203e034ba7ce9d2fcf485609285660d Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 24 Mar 2026 14:38:35 +0100 Subject: [PATCH 07/13] bender-slang(build): Switch to CMake fetch instead of git submodule --- .clangd | 5 ++- .gitmodules | 3 -- crates/bender-slang/CMakeLists.txt | 16 ++++++++ crates/bender-slang/Cargo.toml | 12 ++++++ crates/bender-slang/build.rs | 62 ++++++++++++++++++++++-------- crates/bender-slang/vendor/slang | 1 - 6 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 crates/bender-slang/CMakeLists.txt delete mode 160000 crates/bender-slang/vendor/slang diff --git a/.clangd b/.clangd index e1f5bf28..0588fc3d 100644 --- a/.clangd +++ b/.clangd @@ -6,8 +6,9 @@ CompileFlags: - -fno-cxx-modules - -I. - -I../../../crates - - -I../vendor/slang/include - - -I../vendor/slang/external + - -I../../../target/slang-include + - -I../../../target/slang-external + - -I../../../target/slang-fmt-include - -I../../../target/slang-generated-include - -I../../../target/cxxbridge - -DSLANG_USE_MIMALLOC=1 diff --git a/.gitmodules b/.gitmodules index cccf606d..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "crates/bender-slang/vendor/slang"] - path = crates/bender-slang/vendor/slang - url = https://github.com/MikePopoloski/slang.git diff --git a/crates/bender-slang/CMakeLists.txt b/crates/bender-slang/CMakeLists.txt new file mode 100644 index 00000000..d3270214 --- /dev/null +++ b/crates/bender-slang/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.24) +project(bender-slang-build LANGUAGES CXX) + +include(FetchContent) + +FetchContent_Declare( + slang + GIT_REPOSITORY https://github.com/MikePopoloski/slang.git + GIT_TAG v10.0 + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(slang) + +# Expose slang's external headers (e.g. fmt) so the Rust cxx bridge +# can find them. They are not installed by slang's own install rules. +install(DIRECTORY ${slang_SOURCE_DIR}/external/ DESTINATION slang-external) diff --git a/crates/bender-slang/Cargo.toml b/crates/bender-slang/Cargo.toml index bdf15791..ff28fd5b 100644 --- a/crates/bender-slang/Cargo.toml +++ b/crates/bender-slang/Cargo.toml @@ -2,6 +2,18 @@ name = "bender-slang" version = "0.1.0" edition = "2024" +description = "Rust bindings for the Slang SystemVerilog parser" +license = "Apache-2.0" +authors = ["Tim Fischer "] +repository = "https://github.com/pulp-platform/bender" +readme = "README.md" +include = [ + "src/**", + "cpp/**", + "CMakeLists.txt", + "build.rs", + "README.md", +] [dependencies] cxx = "1.0.194" diff --git a/crates/bender-slang/build.rs b/crates/bender-slang/build.rs index 5a6dc99e..105a53ec 100644 --- a/crates/bender-slang/build.rs +++ b/crates/bender-slang/build.rs @@ -2,10 +2,15 @@ // Tim Fischer #[cfg(unix)] -// We create a symlink from the generated include directory to a stable location in the target directory -// so that tools like clangd can find the headers without needing to know the exact OUT_DIR path. +// We create symlinks from build-time directories to stable locations in the target directory +// so that tools like clangd can find headers without needing to know the exact OUT_DIR path. // This is purely for improving the development experience and is not necessary for the build itself. -fn refresh_include_symlink(generated_include_dir: &std::path::Path) { +fn refresh_include_symlinks( + generated_include_dir: &std::path::Path, + slang_include_dir: &std::path::Path, + slang_external_dir: &std::path::Path, + fmt_include_dir: &std::path::Path, +) { use std::ffi::OsStr; use std::fs; use std::os::unix::fs::symlink; @@ -23,14 +28,26 @@ fn refresh_include_symlink(generated_include_dir: &std::path::Path) { return; }; - let stable_link = target_root.join("slang-generated-include"); - let _ = fs::remove_file(&stable_link); - let _ = fs::remove_dir_all(&stable_link); - let _ = symlink(generated_include_dir, &stable_link); + for (src, name) in [ + (generated_include_dir, "slang-generated-include"), + (slang_include_dir, "slang-include"), + (slang_external_dir, "slang-external"), + (fmt_include_dir, "slang-fmt-include"), + ] { + let stable_link = target_root.join(name); + let _ = fs::remove_file(&stable_link); + let _ = fs::remove_dir_all(&stable_link); + let _ = symlink(src, &stable_link); + } } #[cfg(not(unix))] -fn refresh_include_symlink(_generated_include_dir: &std::path::Path) {} +fn refresh_include_symlinks( + _generated_include_dir: &std::path::Path, + _slang_include_dir: &std::path::Path, + _slang_external_dir: &std::path::Path, + _fmt_include_dir: &std::path::Path, +) {} fn main() { let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); @@ -45,7 +62,7 @@ fn main() { }; // Create the configuration builder - let mut slang_lib = cmake::Config::new("vendor/slang"); + let mut slang_lib = cmake::Config::new("."); // Common defines to give to both Slang and the Bridge // Note: It is very important to provide the same defines and flags @@ -93,13 +110,23 @@ fn main() { // Build the slang library let dst = slang_lib.build(); - let lib_dir = dst.join("lib"); - - // Create a symlink for the generated include directory - refresh_include_symlink(&dst.join("include")); + // With FetchContent, cmake builds slang in a _deps subdirectory rather than + // installing it. Point directly at the FetchContent build/source directories. + let slang_lib_dir = dst.join("build/_deps/slang-build/lib"); + let slang_include_dir = dst.join("build/_deps/slang-src/include"); + let slang_generated_include_dir = dst.join("build/_deps/slang-build/source"); + let fmt_include_dir = dst.join("build/_deps/fmt-src/include"); + + // Create stable symlinks for clangd (see refresh_include_symlinks) + refresh_include_symlinks( + &slang_generated_include_dir, + &slang_include_dir, + &dst.join("slang-external"), + &fmt_include_dir, + ); // Configure Linker to find Slang static library - println!("cargo:rustc-link-search=native={}", lib_dir.display()); + println!("cargo:rustc-link-search=native={}", slang_lib_dir.display()); println!("cargo:rustc-link-lib=static=svlang"); // Link the additional libraries based on build profile. @@ -124,9 +151,10 @@ fn main() { .file("cpp/print.cpp") .file("cpp/analysis.cpp") .flag_if_supported("-std=c++20") - .include("vendor/slang/include") - .include("vendor/slang/external") - .include(dst.join("include")); + .include(&slang_include_dir) + .include(&slang_generated_include_dir) + .include(dst.join("slang-external")) + .include(&fmt_include_dir); // Apply common defines and flags to the bridge build as well for (def, value) in common_cxx_defines.iter() { diff --git a/crates/bender-slang/vendor/slang b/crates/bender-slang/vendor/slang deleted file mode 160000 index ace09c5d..00000000 --- a/crates/bender-slang/vendor/slang +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ace09c5d7c9f4e28eed654d2f353c6dc792ebf67 From 3d4a64c4a2c7fe6141de43d630e254b6e8332203 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 24 Mar 2026 14:47:10 +0100 Subject: [PATCH 08/13] bender-slang: Create `compile_flags.txt` instead of symlinks --- .clangd | 11 ----- .gitignore | 1 + crates/bender-slang/build.rs | 78 ++++++++++++++++-------------------- 3 files changed, 35 insertions(+), 55 deletions(-) diff --git a/.clangd b/.clangd index 0588fc3d..1d99f92a 100644 --- a/.clangd +++ b/.clangd @@ -2,15 +2,4 @@ If: PathMatch: (^|.*/)crates/bender-slang/cpp/.*\.(h|hpp|hh|c|cc|cpp|cxx)$ CompileFlags: Add: - - -std=c++20 - -fno-cxx-modules - - -I. - - -I../../../crates - - -I../../../target/slang-include - - -I../../../target/slang-external - - -I../../../target/slang-fmt-include - - -I../../../target/slang-generated-include - - -I../../../target/cxxbridge - - -DSLANG_USE_MIMALLOC=1 - - -DSLANG_USE_THREADS=1 - - -DSLANG_BOOST_SINGLE_HEADER=1 diff --git a/.gitignore b/.gitignore index eeeddced..2b8399ec 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ tests/**/.bender # clangd .cache/clangd +crates/bender-slang/cpp/compile_flags.txt diff --git a/crates/bender-slang/build.rs b/crates/bender-slang/build.rs index 105a53ec..923dddcc 100644 --- a/crates/bender-slang/build.rs +++ b/crates/bender-slang/build.rs @@ -1,54 +1,38 @@ // Copyright (c) 2025 ETH Zurich // Tim Fischer -#[cfg(unix)] -// We create symlinks from build-time directories to stable locations in the target directory -// so that tools like clangd can find headers without needing to know the exact OUT_DIR path. -// This is purely for improving the development experience and is not necessary for the build itself. -fn refresh_include_symlinks( - generated_include_dir: &std::path::Path, - slang_include_dir: &std::path::Path, - slang_external_dir: &std::path::Path, - fmt_include_dir: &std::path::Path, +// Generates cpp/compile_flags.txt so that clangd gets the correct include paths +// for the C++ bridge files. The file is written to the cpp/ directory and should +// be gitignored. It is picked up automatically by clangd for all files in that directory. +fn generate_compile_flags( + manifest_dir: &std::path::Path, + dst: &std::path::Path, + includes: &[&std::path::Path], + defines: &[(&str, &str)], ) { use std::ffi::OsStr; - use std::fs; - use std::os::unix::fs::symlink; - use std::path::PathBuf; - let Ok(out_dir) = std::env::var("OUT_DIR") else { - return; - }; - let out_dir = PathBuf::from(out_dir); - - let Some(target_root) = out_dir + let Some(target_root) = dst .ancestors() - .find(|path| path.file_name() == Some(OsStr::new("target"))) + .find(|p| p.file_name() == Some(OsStr::new("target"))) else { return; }; - for (src, name) in [ - (generated_include_dir, "slang-generated-include"), - (slang_include_dir, "slang-include"), - (slang_external_dir, "slang-external"), - (fmt_include_dir, "slang-fmt-include"), - ] { - let stable_link = target_root.join(name); - let _ = fs::remove_file(&stable_link); - let _ = fs::remove_dir_all(&stable_link); - let _ = symlink(src, &stable_link); - } + let flags: Vec = ["-x", "c++", "-std=c++20", "-fno-cxx-modules"] + .map(str::to_string) + .into_iter() + .chain(includes.iter().map(|p| format!("-I{}", p.display()))) + .chain([format!("-I{}", target_root.join("cxxbridge").display())]) + .chain(defines.iter().map(|(k, v)| format!("-D{}={}", k, v))) + .collect(); + + let _ = std::fs::write( + manifest_dir.join("cpp/compile_flags.txt"), + flags.join("\n") + "\n", + ); } -#[cfg(not(unix))] -fn refresh_include_symlinks( - _generated_include_dir: &std::path::Path, - _slang_include_dir: &std::path::Path, - _slang_external_dir: &std::path::Path, - _fmt_include_dir: &std::path::Path, -) {} - fn main() { let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); @@ -117,12 +101,18 @@ fn main() { let slang_generated_include_dir = dst.join("build/_deps/slang-build/source"); let fmt_include_dir = dst.join("build/_deps/fmt-src/include"); - // Create stable symlinks for clangd (see refresh_include_symlinks) - refresh_include_symlinks( - &slang_generated_include_dir, - &slang_include_dir, - &dst.join("slang-external"), - &fmt_include_dir, + // Generate cpp/compile_flags.txt for clangd IDE support + let manifest_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + generate_compile_flags( + &manifest_dir, + &dst, + &[ + &slang_include_dir, + &slang_generated_include_dir, + &dst.join("slang-external"), + &fmt_include_dir, + ], + &common_cxx_defines, ); // Configure Linker to find Slang static library From 854defae2093f97fe46dcc7e8df19512b21ed811 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 24 Mar 2026 15:19:18 +0100 Subject: [PATCH 09/13] Cargo.toml: Make `slang` a default feature It is now stable enough in a sense that it compiles on all platform without major issues --- .github/workflows/ci.yml | 6 +++--- .github/workflows/cli_regression.yml | 6 +++--- .github/workflows/release-build.yml | 2 +- Cargo.toml | 1 + dist-workspace.toml | 2 -- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 257e4132..d1402469 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,9 +38,9 @@ jobs: shared-key: ci-test-${{ matrix.os }}-${{ matrix.rust }} cache-workspace-crates: "true" - name: Build - run: cargo build --all-features + run: cargo build - name: Cargo Test - run: cargo test --workspace --all-features + run: cargo test --workspace - name: Run unit-tests run: tests/run_all.sh shell: bash @@ -60,7 +60,7 @@ jobs: with: shared-key: ci-clippy-${{ runner.os }}-stable cache-workspace-crates: "true" - - run: cargo clippy --all-features + - run: cargo clippy unused-deps: name: Unused Dependencies diff --git a/.github/workflows/cli_regression.yml b/.github/workflows/cli_regression.yml index 71b5780a..c01809b5 100644 --- a/.github/workflows/cli_regression.yml +++ b/.github/workflows/cli_regression.yml @@ -19,7 +19,7 @@ jobs: with: toolchain: stable - name: Run CLI Regression - run: cargo test --all-features --test cli_regression -- --ignored + run: cargo test --test cli_regression -- --ignored env: BENDER_TEST_GOLDEN_BRANCH: ${{ github.base_ref }} @@ -33,7 +33,7 @@ jobs: with: toolchain: stable - name: Run CLI Regression - run: cargo test --all-features --test cli_regression -- --ignored + run: cargo test --test cli_regression -- --ignored env: BENDER_TEST_GOLDEN_BRANCH: ${{ github.base_ref }} @@ -47,6 +47,6 @@ jobs: with: toolchain: stable - name: Run CLI Regression - run: cargo test --all-features --test cli_regression -- --ignored + run: cargo test --test cli_regression -- --ignored env: BENDER_TEST_GOLDEN_BRANCH: ${{ github.base_ref }} diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index b3c93240..a0c06689 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -56,4 +56,4 @@ jobs: shared-key: release-build-${{ matrix.target }} cache-workspace-crates: "true" - name: Build (release) - run: cargo build --release --all-features + run: cargo build --release diff --git a/Cargo.toml b/Cargo.toml index 2e05e470..ea4fcc9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,4 +60,5 @@ assert_cmd = "2.1.1" pretty_assertions = "1.4" [features] +default = ["slang"] slang = ["dep:bender-slang"] diff --git a/dist-workspace.toml b/dist-workspace.toml index 3e55bb0a..7e1920c6 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -15,8 +15,6 @@ targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-da install-path = "CARGO_HOME" # Whether to install an updater program install-updater = false -# Whether to pass --all-features to cargo build -all-features = true # Whether +crt-static should be used on msvc msvc-crt-static = false From e40fa864f6484a879d3af1bf18d59b951ec3f241 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 24 Mar 2026 15:45:13 +0100 Subject: [PATCH 10/13] docs: Update README.md to reflect `slang` being a default feature `bender-slang` being an internal crate --- README.md | 2 +- crates/bender-slang/Cargo.toml | 2 +- crates/bender-slang/README.md | 12 ++++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8bffe203..30186e50 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ To build from source, [install Rust][rust-installation] and run: ```sh cargo install bender ``` -To enable optional features (including the Slang-backed `pickle` command), add `--all-features`. This may increase build time and require additional build dependencies. +The Slang-backed `pickle` command is included by default. It requires a C++20 compiler and increases build time significantly. To build without it, use `--no-default-features`. Some Linux distributions also have Bender in their repositories: diff --git a/crates/bender-slang/Cargo.toml b/crates/bender-slang/Cargo.toml index ff28fd5b..00ad6b3d 100644 --- a/crates/bender-slang/Cargo.toml +++ b/crates/bender-slang/Cargo.toml @@ -2,7 +2,7 @@ name = "bender-slang" version = "0.1.0" edition = "2024" -description = "Rust bindings for the Slang SystemVerilog parser" +description = "Internal bender crate: Rust bindings for the Slang SystemVerilog parser" license = "Apache-2.0" authors = ["Tim Fischer "] repository = "https://github.com/pulp-platform/bender" diff --git a/crates/bender-slang/README.md b/crates/bender-slang/README.md index a873debf..7e29b5d1 100644 --- a/crates/bender-slang/README.md +++ b/crates/bender-slang/README.md @@ -1,8 +1,12 @@ # bender-slang -`bender-slang` provides the C++ bridge between `bender` and the [Slang](https://github.com/MikePopoloski/slang) parser infrastructure, included as a submodule. +> **Internal crate:** `bender-slang` is an internal crate of [Bender](https://github.com/pulp-platform/bender). It does not provide a stable public API — breaking changes may occur at any time without notice. -It is used by Bender's optional Slang-backed features, most notably the `pickle` command. +`bender-slang` provides the C++ bridge between `bender` and the [Slang](https://github.com/MikePopoloski/slang) SystemVerilog parser. It is used by Bender's `pickle` command. + +## Building + +Building this crate requires a C++20-capable compiler and CMake. The Slang library and its dependencies (fmt, mimalloc) are fetched and built automatically via CMake's FetchContent — no manual setup is required. ## IIS Environment Setup @@ -12,8 +16,8 @@ In the IIS environment on Linux, a newer GCC toolchain is required to build `ben cp .cargo/config.toml.iis .cargo/config.toml ``` -Then, you can build or install bender with the usual Cargo command: +Then, build or install bender with the usual Cargo command: ```sh -cargo install --path . --features slang +cargo install --path . ``` From 4dc520305e7decaf8c2d0cbf89acc186eff9ac63 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 24 Mar 2026 18:09:08 +0100 Subject: [PATCH 11/13] bender-slang(build): Don't generate `compile_flags.txt` in publish mode --- crates/bender-slang/Cargo.toml | 2 ++ crates/bender-slang/build.rs | 31 +++++++++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/crates/bender-slang/Cargo.toml b/crates/bender-slang/Cargo.toml index 00ad6b3d..037b729a 100644 --- a/crates/bender-slang/Cargo.toml +++ b/crates/bender-slang/Cargo.toml @@ -10,6 +10,8 @@ readme = "README.md" include = [ "src/**", "cpp/**", + "!cpp/compile_flags.txt", + "tests/**", "CMakeLists.txt", "build.rs", "README.md", diff --git a/crates/bender-slang/build.rs b/crates/bender-slang/build.rs index 923dddcc..a9228691 100644 --- a/crates/bender-slang/build.rs +++ b/crates/bender-slang/build.rs @@ -34,6 +34,12 @@ fn generate_compile_flags( } fn main() { + let manifest_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + + // .cargo_vcs_info.json is placed in the package root by cargo during packaging/publish. + // Writing outside OUT_DIR is forbidden in that context, so skip the clangd helper. + let in_publish = manifest_dir.join(".cargo_vcs_info.json").exists(); + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); let build_profile = std::env::var("PROFILE").unwrap(); @@ -102,18 +108,19 @@ fn main() { let fmt_include_dir = dst.join("build/_deps/fmt-src/include"); // Generate cpp/compile_flags.txt for clangd IDE support - let manifest_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); - generate_compile_flags( - &manifest_dir, - &dst, - &[ - &slang_include_dir, - &slang_generated_include_dir, - &dst.join("slang-external"), - &fmt_include_dir, - ], - &common_cxx_defines, - ); + if !in_publish { + generate_compile_flags( + &manifest_dir, + &dst, + &[ + &slang_include_dir, + &slang_generated_include_dir, + &dst.join("slang-external"), + &fmt_include_dir, + ], + &common_cxx_defines, + ); + } // Configure Linker to find Slang static library println!("cargo:rustc-link-search=native={}", slang_lib_dir.display()); From ffa305914952458bdd35292121540fbba4d5b94c Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 24 Mar 2026 18:35:26 +0100 Subject: [PATCH 12/13] Cargo.toml: Use published `bender-slang` Actually, when building locally, cargo will still opt for `path`, meaning local development should work out of the box. For installation e.g. `cargo install bender` there is no local `bender-slang` dependency and it will pull it from crates.io --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ea4fcc9a..fbed2a78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ inherits = "release" lto = "thin" [dependencies] -bender-slang = { path = "crates/bender-slang", optional = true} +bender-slang = { version = "0.1.0", path = "crates/bender-slang", optional = true } serde = { version = "1", features = ["derive"] } serde_yaml_ng = "0.10" From 974800eeaa1c51cf5e94cb4224a4ca7f8c0b20ad Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Wed, 25 Mar 2026 16:55:38 +0100 Subject: [PATCH 13/13] bender-slang: Don't distribute when tagged --- crates/bender-slang/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bender-slang/Cargo.toml b/crates/bender-slang/Cargo.toml index 037b729a..005d3fc3 100644 --- a/crates/bender-slang/Cargo.toml +++ b/crates/bender-slang/Cargo.toml @@ -27,3 +27,6 @@ dunce = "1.0.4" [build-dependencies] cmake = "0.1.57" cxx-build = "1.0.194" + +[package.metadata.dist] +dist = false