diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..4e0c9e095 --- /dev/null +++ b/.clang-format @@ -0,0 +1,18 @@ +--- +Language: Cpp +BasedOnStyle: LLVM + +# 4 spaces everywhere +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ContinuationIndentWidth: 4 + +# Modern C++ style +Standard: c++20 +ColumnLimit: 120 +PointerAlignment: Left + +# Organize includes +SortIncludes: true +IncludeBlocks: Regroup diff --git a/.clangd b/.clangd new file mode 100644 index 000000000..e1f5bf28a --- /dev/null +++ b/.clangd @@ -0,0 +1,15 @@ +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../vendor/slang/include + - -I../vendor/slang/external + - -I../../../target/slang-generated-include + - -I../../../target/cxxbridge + - -DSLANG_USE_MIMALLOC=1 + - -DSLANG_USE_THREADS=1 + - -DSLANG_BOOST_SINGLE_HEADER=1 diff --git a/.github/scripts/gen_dockerfile.sh b/.github/scripts/gen_dockerfile.sh deleted file mode 100755 index 512e9700e..000000000 --- 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 index ed3ab3b9d..6d966396b 100755 --- a/.github/scripts/package.sh +++ b/.github/scripts/package.sh @@ -1,39 +1,58 @@ #!/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) +set -euo pipefail + +usage() { + cat >&2 <<'EOF' +usage: .github/scripts/package.sh [flavor] + +Examples: + .github/scripts/package.sh x86_64-linux-gnu target/release/bender + .github/scripts/package.sh x86_64-unknown-linux-gnu target/release/bender slang + .github/scripts/package.sh universal-apple-darwin target/universal2-apple-darwin/release/bender slang + .github/scripts/package.sh x86_64-pc-windows-msvc target/release/bender.exe slang +EOF +} + +if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then + usage + exit 1 +fi + +artifact_target="$1" +binary_path="$2" +flavor="${3:-}" if [[ "$GITHUB_REF" =~ ^refs/tags/v.*$ ]]; then - pkgver="$(echo $GITHUB_REF | sed -n 's/^refs\/tags\/v//p')" + pkgver="$(echo "$GITHUB_REF" | sed -n 's/^refs\/tags\/v//p')" else - pkgver="$(echo $GITHUB_REF | sed -n 's/^refs\/tags\///p')" + 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" +if [ ! -f "$binary_path" ]; then + echo "error: binary '$binary_path' not found" >&2 + exit 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" +if [ -n "$flavor" ]; then + flavor_suffix="+$flavor" +else + flavor_suffix="" +fi + +artifact_name="bender-$pkgver-$artifact_target$flavor_suffix.tar.gz" +bin_dir="$(dirname "$binary_path")" +bin_name="$(basename "$binary_path")" + +if command -v gtar >/dev/null 2>&1; then + tar_cmd="gtar" +else + tar_cmd="tar" fi -tar -czf "bender-$pkgver$tar_prefix-linux-gnu$tar_suffix.tar.gz" \ - -C "./$release_dir" \ - --owner=0 --group=0 \ - bender +"$tar_cmd" -czf "$artifact_name" -C "$bin_dir" --owner=0 --group=0 "$bin_name" +echo "Created $artifact_name" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4420a4f4e..ae2ea81d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,12 @@ on: push: branches: [master] pull_request: - branches: [master] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest @@ -26,13 +29,14 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust}} - components: rustfmt + - 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: Format (fix with `cargo fmt`) - run: cargo fmt -- --check - name: Run unit-tests run: tests/run_all.sh shell: bash @@ -46,6 +50,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 @@ -63,6 +71,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 @@ -71,6 +83,30 @@ jobs: run: tests/run_all.sh shell: bash + release-build: + name: Release Build (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + 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-release-build-${{ runner.os }}-stable + cache-workspace-crates: "true" + - name: Build (release) + run: cargo build --release --all-features + clippy_check: name: Clippy runs-on: ubuntu-latest @@ -82,6 +118,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: @@ -92,6 +132,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 diff --git a/.github/workflows/cli_regression.yml b/.github/workflows/cli_regression.yml index bfdcf9bd7..71b5780a8 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 diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml new file mode 100644 index 000000000..933abbc29 --- /dev/null +++ b/.github/workflows/dist.yml @@ -0,0 +1,30 @@ +name: dist + +on: + workflow_dispatch: + pull_request: + paths: + - "Cargo.toml" + - "Cargo.lock" + - "src/**" + - "crates/**" + - ".github/workflows/dist.yml" + +jobs: + plan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - name: Install cargo-dist + run: | + curl --proto '=https' --tlsv1.2 --tls-max 1.2 -LsSf \ + https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh + - name: Show cargo-dist version + run: cargo dist --version + - name: Plan release + run: cargo dist plan diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml new file mode 100644 index 000000000..f81cf6392 --- /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" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2df026bd0..8acd61590 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,294 +4,181 @@ on: release: types: [created] workflow_dispatch: + inputs: + publish_assets: + description: "Upload release assets" + required: false + default: false + type: boolean jobs: - release_amd64: + release-linux-compat-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 + - uses: actions/checkout@v6 + with: + submodules: recursive + - name: Build (old-glibc baseline) 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; + -v "$GITHUB_WORKSPACE/target/amd64:/source/target" \ + --platform linux/amd64 \ + quay.io/pypa/manylinux2014_x86_64 \ + /bin/bash -lc ' + set -euo pipefail + export RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo + export PATH=$CARGO_HOME/bin:$PATH + mkdir -p "$CARGO_HOME" "$RUSTUP_HOME" + curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + cd /source + cargo build --release + ' + - name: Create Package + run: .github/scripts/package.sh x86_64-linux-gnu target/amd64/release/bender shell: bash - name: Upload Release Asset + if: ${{ github.event_name == 'release' || inputs.publish_assets }} uses: softprops/action-gh-release@v1 with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz + tag_name: ${{ github.event.release.tag_name || github.ref_name }} + files: bender-*-x86_64-linux-gnu.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release_arm64: + + release-linux-compat-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 + - uses: actions/checkout@v6 + with: + submodules: recursive + - name: Build (old-glibc baseline) 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; + -v "$GITHUB_WORKSPACE/target/arm64:/source/target" \ + --platform linux/arm64 \ + quay.io/pypa/manylinux2014_aarch64 \ + /bin/bash -lc ' + set -euo pipefail + export RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo + export PATH=$CARGO_HOME/bin:$PATH + mkdir -p "$CARGO_HOME" "$RUSTUP_HOME" + curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + cd /source + cargo build --release + ' + - name: Create Package + run: .github/scripts/package.sh arm64-linux-gnu target/arm64/release/bender shell: bash - name: Upload Release Asset + if: ${{ github.event_name == 'release' || inputs.publish_assets }} uses: softprops/action-gh-release@v1 with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz + tag_name: ${{ github.event.release.tag_name || github.ref_name }} + files: bender-*-arm64-linux-gnu.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release-gnu_amd64: + + release-linux-modern-amd64: runs-on: ubuntu-latest - # Use container that supports old GLIBC versions and (hopefully) many linux OSs - # container: quay.io/pypa/manylinux2014_x86_64 + container: + image: quay.io/pypa/manylinux_2_28_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 + - uses: actions/checkout@v6 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - name: Build (all features, manylinux_2_28) + run: cargo build --release --all-features + - name: Create Package shell: bash + run: .github/scripts/package.sh x86_64-unknown-linux-gnu target/release/bender slang - name: Upload Release Asset + if: ${{ github.event_name == 'release' || inputs.publish_assets }} uses: softprops/action-gh-release@v1 with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz + tag_name: ${{ github.event.release.tag_name || github.ref_name }} + files: bender-*-x86_64-unknown-linux-gnu+slang.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release-gnu_arm64: + + release-linux-modern-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 + container: + image: quay.io/pypa/manylinux_2_28_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 + - uses: actions/checkout@v6 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - name: Build (all features, manylinux_2_28) + run: cargo build --release --all-features + - name: Create Package shell: bash + run: .github/scripts/package.sh aarch64-unknown-linux-gnu target/release/bender slang - name: Upload Release Asset + if: ${{ github.event_name == 'release' || inputs.publish_assets }} uses: softprops/action-gh-release@v1 with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz + tag_name: ${{ github.event.release.tag_name || github.ref_name }} + files: bender-*-aarch64-unknown-linux-gnu+slang.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 + - uses: actions/checkout@v6 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable - name: universal2 install run: | rustup target add x86_64-apple-darwin rustup target add aarch64-apple-darwin cargo install universal2 - - name: MacOS Build + - name: Build (all features) 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 + shell: bash + run: .github/scripts/package.sh universal-apple-darwin target/universal2-apple-darwin/release/bender slang - name: Upload Release Asset + if: ${{ github.event_name == 'release' || inputs.publish_assets }} uses: softprops/action-gh-release@v1 with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz + tag_name: ${{ github.event.release.tag_name || github.ref_name }} + files: bender-*-universal-apple-darwin+slang.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 + - uses: actions/checkout@v6 + with: + submodules: recursive + - 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: Build (all features) + run: cargo build --release --all-features - 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 + shell: bash + run: .github/scripts/package.sh x86_64-pc-windows-msvc target/release/bender.exe slang - name: Upload Release Asset + if: ${{ github.event_name == 'release' || inputs.publish_assets }} uses: softprops/action-gh-release@v1 with: - tag_name: ${{ github.event.release.tag_name }} - files: bender-*.tar.gz + tag_name: ${{ github.event.release.tag_name || github.ref_name }} + files: bender-*-x86_64-pc-windows-msvc+slang.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 000000000..4b6dafa14 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,297 @@ +# 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: + workflow_dispatch: + 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_name == 'push' && github.ref_name || '' }} + tag-flag: ${{ github.event_name == 'push' && format('--tag={0}', github.ref_name) || '' }} + publishing: ${{ github.event_name == 'push' }} + 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_name == 'push' && 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/.gitignore b/.gitignore index 796da3f24..eeeddced9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ -.* -!/.ci/ -!.git* -!.travis.yml -/target -/tests/tmp +# Cargo build files +target + +# Temporary test files +tests/**/tmp +tests/**/.bender + +# clangd +.cache/clangd diff --git a/Cargo.toml b/Cargo.toml index eeca629ff..81c2908cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,23 @@ rust-version = "1.87.0" [workspace] members = ["crates/bender-slang"] +[profile.dist] +inherits = "release" +lto = "thin" + +[workspace.metadata.dist] +cargo-dist-version = "0.31.0" +ci = ["github"] +installers = ["shell", "powershell"] +targets = [ + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "aarch64-apple-darwin", + "x86_64-pc-windows-msvc", +] +allow-dirty = ["ci"] + [dependencies] bender-slang = { path = "crates/bender-slang", optional = true} diff --git a/README.md b/README.md index a859c21b4..c3135feca 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,12 @@ curl --proto '=https' --tlsv1.2 https://pulp-platform.github.io/bender/init -sSf ``` 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]. +As an alternative binary installer path, we are migrating releases to [`cargo-dist`](https://github.com/axodotdev/cargo-dist). If you already use [`cargo-binstall`](https://github.com/cargo-bins/cargo-binstall), you can install via: +```sh +cargo binstall bender +``` +For now, both approaches are supported. + 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: ```sh cargo install bender diff --git a/crates/bender-slang/build.rs b/crates/bender-slang/build.rs index 7db0396f7..faf39b8f3 100644 --- a/crates/bender-slang/build.rs +++ b/crates/bender-slang/build.rs @@ -1,6 +1,37 @@ // Copyright (c) 2025 ETH Zurich // 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. +// 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) { + 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 + .ancestors() + .find(|path| path.file_name() == Some(OsStr::new("target"))) + else { + 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); +} + +#[cfg(not(unix))] +fn refresh_include_symlink(_generated_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(); @@ -64,6 +95,11 @@ fn main() { let dst = slang_lib.build(); let lib_dir = dst.join("lib"); + // Create a symlink for the generated include directory + if target_os == "linux" || target_os == "macos" { + refresh_include_symlink(&dst.join("include")); + } + // Configure Linker to find Slang static library println!("cargo:rustc-link-search=native={}", lib_dir.display()); println!("cargo:rustc-link-lib=static=svlang"); @@ -97,7 +133,7 @@ fn main() { let compiler = std::env::var("CXX").unwrap_or_else(|_| "g++".to_string()); // We search for the static libstdc++ file using g++ let output = std::process::Command::new(&compiler) - .args(&["-print-file-name=libstdc++.a"]) + .args(["-print-file-name=libstdc++.a"]) .output() .expect("Failed to run g++"); diff --git a/crates/bender-slang/cpp/slang_bridge.cpp b/crates/bender-slang/cpp/slang_bridge.cpp index 755690992..7e7c02eb7 100644 --- a/crates/bender-slang/cpp/slang_bridge.cpp +++ b/crates/bender-slang/cpp/slang_bridge.cpp @@ -4,8 +4,6 @@ #include "slang_bridge.h" #include "bender-slang/src/lib.rs.h" -#include "slang/diagnostics/DiagnosticEngine.h" -#include "slang/diagnostics/TextDiagnosticClient.h" #include "slang/syntax/CSTSerializer.h" #include "slang/syntax/SyntaxPrinter.h" #include "slang/syntax/SyntaxVisitor.h" @@ -17,7 +15,6 @@ #include using namespace slang; -using namespace slang::driver; using namespace slang::syntax; using namespace slang::parsing; diff --git a/crates/bender-slang/cpp/slang_bridge.h b/crates/bender-slang/cpp/slang_bridge.h index a309b5a95..faa4431d1 100644 --- a/crates/bender-slang/cpp/slang_bridge.h +++ b/crates/bender-slang/cpp/slang_bridge.h @@ -7,13 +7,13 @@ #include "rust/cxx.h" #include "slang/diagnostics/DiagnosticEngine.h" #include "slang/diagnostics/TextDiagnosticClient.h" -#include "slang/driver/Driver.h" +#include "slang/parsing/Preprocessor.h" #include "slang/syntax/SyntaxTree.h" +#include "slang/text/SourceManager.h" #include #include #include -#include #include struct SlangPrintOpts; diff --git a/crates/bender-slang/tests/basic.rs b/crates/bender-slang/tests/basic.rs new file mode 100644 index 000000000..03d45538f --- /dev/null +++ b/crates/bender-slang/tests/basic.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; + +fn fixture_path(rel: &str) -> String { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join("tests/pickle") + .join(rel) + .canonicalize() + .expect("valid fixture path") + .to_string_lossy() + .into_owned() +} + +#[test] +fn parse_valid_file_succeeds() { + let mut session = bender_slang::SlangSession::new(); + let files = vec![fixture_path("src/top.sv")]; + let includes = vec![fixture_path("include")]; + let defines = vec![]; + assert!(session.parse_group(&files, &includes, &defines).is_ok()); + assert_eq!(session.tree_count(), 1); +} + +#[test] +fn parse_invalid_file_returns_parse_error() { + let mut session = bender_slang::SlangSession::new(); + let files = vec![fixture_path("src/broken.sv")]; + let includes = vec![]; + let defines = vec![]; + let result = session.parse_group(&files, &includes, &defines); + + match result { + Err(bender_slang::SlangError::ParseGroup { .. }) => {} + Err(other) => panic!("expected SlangError::ParseGroup, got {other}"), + Ok(_) => panic!("expected parse to fail"), + } +} diff --git a/tests/cli_regression.rs b/tests/cli_regression.rs index c75b37bfe..66f652eeb 100644 --- a/tests/cli_regression.rs +++ b/tests/cli_regression.rs @@ -161,5 +161,8 @@ regression_tests! { packages: &["packages"], packages_graph: &["packages", "--graph"], packages_flat: &["packages", "--flat"], + // Enable once the golden binary is built with `slang` support. + // pickle_basic: &["pickle", "--target", "top"], + // pickle_top_trim: &["pickle", "--target", "top", "--top", "top"], } diff --git a/tests/pickle.rs b/tests/pickle.rs new file mode 100644 index 000000000..307b8bf2b --- /dev/null +++ b/tests/pickle.rs @@ -0,0 +1,75 @@ +// Copyright (c) 2025 ETH Zurich +// Tim Fischer + +#[cfg(feature = "slang")] +mod tests { + use assert_cmd::cargo; + + fn run_pickle(args: &[&str]) -> String { + let mut full_args = vec!["-d", "tests/pickle", "pickle"]; + full_args.extend(args); + + let out = cargo::cargo_bin_cmd!() + .args(&full_args) + .output() + .expect("Failed to execute bender binary"); + + assert!( + out.status.success(), + "pickle command failed.\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&out.stdout), + String::from_utf8_lossy(&out.stderr) + ); + + String::from_utf8(out.stdout).expect("stdout must be utf-8") + } + + #[test] + fn pickle_top_trim_filters_unreachable_modules() { + let full = run_pickle(&["--target", "top"]); + assert!(full.contains("module unused_top;")); + assert!(full.contains("module unused_leaf;")); + + let trimmed = run_pickle(&["--target", "top", "--top", "top"]); + assert!(trimmed.contains("module top (")); + assert!(trimmed.contains("module core;")); + assert!(trimmed.contains("module leaf;")); + assert!(!trimmed.contains("module unused_top;")); + assert!(!trimmed.contains("module unused_leaf;")); + } + + #[test] + fn pickle_rename_applies_prefix_and_suffix() { + let renamed = run_pickle(&[ + "--target", "top", "--top", "top", "--prefix", "p_", "--suffix", "_s", + ]); + + assert!(renamed.contains("module p_top_s (")); + assert!(renamed.contains("module p_core_s;")); + assert!(renamed.contains("module p_leaf_s;")); + } + + #[test] + fn pickle_exclude_rename_keeps_selected_names() { + let renamed = run_pickle(&[ + "--target", + "top", + "--top", + "top", + "--prefix", + "p_", + "--suffix", + "_s", + "--exclude-rename", + "top", + "--exclude-rename", + "core", + ]); + + assert!(renamed.contains("module top (")); + assert!(renamed.contains("module core;")); + assert!(renamed.contains("module p_leaf_s;")); + assert!(!renamed.contains("module p_top_s (")); + assert!(!renamed.contains("module p_core_s;")); + } +} diff --git a/tests/pickle/Bender.lock b/tests/pickle/Bender.lock new file mode 100644 index 000000000..c33c0b6df --- /dev/null +++ b/tests/pickle/Bender.lock @@ -0,0 +1 @@ +packages: {} diff --git a/tests/pickle/Bender.yml b/tests/pickle/Bender.yml new file mode 100644 index 000000000..c5724f952 --- /dev/null +++ b/tests/pickle/Bender.yml @@ -0,0 +1,19 @@ +package: + name: pickle_repo + +sources: + - defines: + ENABLE_LOGGING: 1 + files: + - src/common_pkg.sv + - src/bus_intf.sv + - src/leaf.sv + - src/core.sv + - src/unused_leaf.sv + - src/unused_top.sv + + - target: top + include_dirs: + - include + files: + - src/top.sv diff --git a/tests/pickle/include/macros.svh b/tests/pickle/include/macros.svh new file mode 100644 index 000000000..a041281aa --- /dev/null +++ b/tests/pickle/include/macros.svh @@ -0,0 +1,6 @@ +// Simple macro to test if includes are resolved correctly +`define LOG(msg) \ + $display("[LOG]: %s", msg); + +// A constant used in the RTL +localparam int unsigned DataWidth = 32; diff --git a/tests/pickle/src/broken.sv b/tests/pickle/src/broken.sv new file mode 100644 index 000000000..0fbcdfa50 --- /dev/null +++ b/tests/pickle/src/broken.sv @@ -0,0 +1,2 @@ +module broken(; +endmodule diff --git a/tests/pickle/src/bus_intf.sv b/tests/pickle/src/bus_intf.sv new file mode 100644 index 000000000..bcd581028 --- /dev/null +++ b/tests/pickle/src/bus_intf.sv @@ -0,0 +1,21 @@ +interface bus_intf #( + parameter int Width = 32 +) ( + input logic clk +); + logic [Width-1:0] addr; + logic [Width-1:0] data; + logic valid; + logic ready; + + modport master ( + output addr, data, valid, + input ready + ); + + modport slave ( + input addr, data, valid, + output ready + ); + +endinterface diff --git a/tests/pickle/src/common_pkg.sv b/tests/pickle/src/common_pkg.sv new file mode 100644 index 000000000..7a2d02d59 --- /dev/null +++ b/tests/pickle/src/common_pkg.sv @@ -0,0 +1,13 @@ +package common_pkg; + + typedef enum logic [1:0] { + Idle = 2'b00, + Busy = 2'b01, + Error = 2'b11 + } state_t; + + function automatic logic is_error(state_t s); + return s == Error; + endfunction + +endpackage diff --git a/tests/pickle/src/core.sv b/tests/pickle/src/core.sv new file mode 100644 index 000000000..ce4f14c49 --- /dev/null +++ b/tests/pickle/src/core.sv @@ -0,0 +1,3 @@ +module core; + leaf u_leaf(); +endmodule diff --git a/tests/pickle/src/leaf.sv b/tests/pickle/src/leaf.sv new file mode 100644 index 000000000..5a7a547a2 --- /dev/null +++ b/tests/pickle/src/leaf.sv @@ -0,0 +1,2 @@ +module leaf; +endmodule diff --git a/tests/pickle/src/top.sv b/tests/pickle/src/top.sv new file mode 100644 index 000000000..2365ab895 --- /dev/null +++ b/tests/pickle/src/top.sv @@ -0,0 +1,39 @@ +`include "macros.svh" + +import common_pkg::*; + +module top ( + input logic clk, + input logic rst_n +); + + core u_core(); + + // Interface Instantiation + bus_intf #(.WIDTH(DATA_WIDTH)) axi_bus ( + .clk(clk) + ); + + // Virtual Interface Type + virtual bus_intf v_if_handle; + + initial begin + v_if_handle = axi_bus; + +`ifdef ENABLE_LOGGING + `LOG("TopModule started successfully!") +`endif + end + + // Type Usage from Package (state_t) + common_pkg::state_t current_state; + + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + current_state <= Idle; + end else begin + current_state <= Busy; + end + end + +endmodule diff --git a/tests/pickle/src/unused_leaf.sv b/tests/pickle/src/unused_leaf.sv new file mode 100644 index 000000000..f7d261d00 --- /dev/null +++ b/tests/pickle/src/unused_leaf.sv @@ -0,0 +1,2 @@ +module unused_leaf; +endmodule diff --git a/tests/pickle/src/unused_top.sv b/tests/pickle/src/unused_top.sv new file mode 100644 index 000000000..a62e36504 --- /dev/null +++ b/tests/pickle/src/unused_top.sv @@ -0,0 +1,3 @@ +module unused_top; + unused_leaf u_unused_leaf(); +endmodule