diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 00000000..633d971c --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,440 @@ +# Build karva on all platforms. +# +# Generates both wheels (for PyPI) and archived binaries (for GitHub releases). +# +# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local +# artifacts job within `cargo-dist`. +name: "Build binaries" + +on: + workflow_call: + inputs: + plan: + required: true + type: string + pull_request: + paths: + # When we change pyproject.toml, we want to ensure that the maturin builds still work. + - pyproject.toml + # And when we change this workflow itself... + - .github/workflows/build-binaries.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +env: + PACKAGE_NAME: karva + MODULE_NAME: karva + PYTHON_VERSION: "3.10" + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + CARGO_TERM_COLOR: always + RUSTUP_MAX_RETRIES: 10 + +jobs: + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: "Build sdist" + uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 + with: + command: sdist + args: --out dist + + - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + with: + python-version: ${{ matrix.python-version }} + + - name: "Test sdist" + run: | + uv venv + uv pip install dist/"${PACKAGE_NAME}"-*.tar.gz --force-reinstall + uv run "${MODULE_NAME}" --help + + - name: "Upload sdist" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: wheels-sdist + path: dist + + macos-x86_64: + runs-on: macos-14 + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + architecture: x64 + + - name: "Build wheels - x86_64" + uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 + with: + target: x86_64 + args: --release --locked --out dist + - name: "Upload wheels" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: wheels-macos-x86_64 + path: dist + - name: "Build binary" + run: cargo build -p karva --release --locked --target x86_64-apple-darwin + - name: "Archive binary" + run: | + TARGET=x86_64-apple-darwin + ARCHIVE_NAME=karva-$TARGET + ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz + + mkdir -p $ARCHIVE_NAME + cp target/$TARGET/release/karva $ARCHIVE_NAME/karva + tar czvf $ARCHIVE_FILE $ARCHIVE_NAME + shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 + - name: "Upload binary" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: artifacts-macos-x86_64 + path: | + *.tar.gz + *.sha256 + + macos-aarch64: + runs-on: macos-14 + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + architecture: arm64 + + - name: "Build wheels - aarch64" + uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 + with: + target: aarch64 + args: --release --locked --out dist + - name: "Upload wheels" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: wheels-aarch64-apple-darwin + path: dist + - name: "Build binary" + run: cargo build -p karva --release --locked --target aarch64-apple-darwin + - name: "Archive binary" + run: | + TARGET=aarch64-apple-darwin + ARCHIVE_NAME=karva-$TARGET + ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz + + mkdir -p $ARCHIVE_NAME + cp target/$TARGET/release/karva $ARCHIVE_NAME/karva + tar czvf $ARCHIVE_FILE $ARCHIVE_NAME + shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 + - name: "Upload binary" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: artifacts-aarch64-apple-darwin + path: | + *.tar.gz + *.sha256 + + windows: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + platform: + - target: x86_64-pc-windows-msvc + arch: x64 + - target: i686-pc-windows-msvc + arch: x86 + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + architecture: ${{ matrix.platform.arch }} + - name: "Build wheels" + uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 + with: + target: ${{ matrix.platform.target }} + args: --release --locked --out dist + - name: "Upload wheels" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: wheels-${{ matrix.platform.target }} + path: dist + - name: "Build binary" + run: cargo build -p karva --release --locked --target ${{ matrix.platform.target }} + + - name: "Archive binary" + shell: bash + run: | + ARCHIVE_FILE=karva-${{ matrix.platform.target }}.zip + 7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/karva.exe + sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 + - name: "Upload binary" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: artifacts-${{ matrix.platform.target }} + path: | + *.zip + *.sha256 + + linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - x86_64-unknown-linux-gnu + - i686-unknown-linux-gnu + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + architecture: x64 + - name: "Build wheels" + uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 + with: + target: ${{ matrix.target }} + manylinux: auto + args: --release --locked --out dist + - name: "Upload wheels" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: wheels-${{ matrix.target }} + path: dist + - name: "Build binary" + run: | + rustup target add ${{ matrix.target }} + cargo build -p karva --release --locked --target ${{ matrix.target }} + - name: "Archive binary" + shell: bash + run: | + set -euo pipefail + + TARGET=${{ matrix.target }} + ARCHIVE_NAME=karva-$TARGET + ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz + + mkdir -p $ARCHIVE_NAME + cp target/$TARGET/release/karva $ARCHIVE_NAME/karva + tar czvf $ARCHIVE_FILE $ARCHIVE_NAME + shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 + - name: "Upload binary" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: artifacts-${{ matrix.target }} + path: | + *.tar.gz + *.sha256 + + linux-cross: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - target: aarch64-unknown-linux-gnu + arch: aarch64 + # see https://github.com/astral-sh/karva/issues/3791 + # and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963 + maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 + - target: armv7-unknown-linux-gnueabihf + arch: armv7 + - target: s390x-unknown-linux-gnu + arch: s390x + - target: powerpc64le-unknown-linux-gnu + arch: ppc64le + # see https://github.com/astral-sh/karva/issues/10073 + maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 + - target: powerpc64-unknown-linux-gnu + arch: ppc64 + # see https://github.com/astral-sh/karva/issues/10073 + maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 + - target: arm-unknown-linux-musleabihf + arch: arm + - target: riscv64gc-unknown-linux-gnu + arch: riscv64 + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: "Build wheels" + uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 + with: + target: ${{ matrix.platform.target }} + manylinux: auto + docker-options: ${{ matrix.platform.maturin_docker_options }} + args: --release --locked --out dist + + - name: "Upload wheels" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: wheels-${{ matrix.platform.target }} + path: dist + - name: "Build binary" + run: | + rustup target add ${{ matrix.platform.target }} + cargo build -p karva --release --locked --target ${{ matrix.platform.target }} + - name: "Archive binary" + shell: bash + run: | + set -euo pipefail + + TARGET=${{ matrix.platform.target }} + ARCHIVE_NAME=karva-$TARGET + ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz + + mkdir -p $ARCHIVE_NAME + cp target/$TARGET/release/karva $ARCHIVE_NAME/karva + tar czvf $ARCHIVE_FILE $ARCHIVE_NAME + shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 + - name: "Upload binary" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: artifacts-${{ matrix.platform.target }} + path: | + *.tar.gz + *.sha256 + + musllinux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - x86_64-unknown-linux-musl + - i686-unknown-linux-musl + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + architecture: x64 + + - name: "Build wheels" + uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 + with: + target: ${{ matrix.target }} + manylinux: musllinux_1_2 + args: --release --locked --out dist + + - name: "Upload wheels" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: wheels-${{ matrix.target }} + path: dist + - name: "Build binary" + run: | + rustup target add ${{ matrix.target }} + cargo build -p karva --release --locked --target ${{ matrix.target }} + - name: "Archive binary" + shell: bash + run: | + set -euo pipefail + + TARGET=${{ matrix.target }} + ARCHIVE_NAME=karva-$TARGET + ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz + + mkdir -p $ARCHIVE_NAME + cp target/$TARGET/release/karva $ARCHIVE_NAME/karva + tar czvf $ARCHIVE_FILE $ARCHIVE_NAME + shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 + - name: "Upload binary" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: artifacts-${{ matrix.target }} + path: | + *.tar.gz + *.sha256 + + musllinux-cross: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - target: aarch64-unknown-linux-musl + arch: aarch64 + maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 + - target: armv7-unknown-linux-musleabihf + arch: armv7 + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + persist-credentials: false + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: "Build wheels" + uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 + with: + target: ${{ matrix.platform.target }} + manylinux: musllinux_1_2 + args: --release --locked --out dist + docker-options: ${{ matrix.platform.maturin_docker_options }} + - name: "Upload wheels" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: wheels-${{ matrix.platform.target }} + path: dist + - name: "Build binary" + run: | + rustup target add ${{ matrix.platform.target }} + cargo build -p karva --release --locked --target ${{ matrix.platform.target }} + - name: "Archive binary" + shell: bash + run: | + set -euo pipefail + + TARGET=${{ matrix.platform.target }} + ARCHIVE_NAME=karva-$TARGET + ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz + + mkdir -p $ARCHIVE_NAME + cp target/$TARGET/release/karva $ARCHIVE_NAME/karva + tar czvf $ARCHIVE_FILE $ARCHIVE_NAME + shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 + - name: "Upload binary" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: artifacts-${{ matrix.platform.target }} + path: | + *.tar.gz + *.sha256 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d5f4dbf6..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,190 +0,0 @@ -name: Build / Release PyPI - -on: - push: - tags: - - v[0-9]*.[0-9]*.[0-9]* - - workflow_call: - -permissions: {} - -jobs: - linux: - name: build wheels (Linux, ${{ matrix.platform.target }}) - runs-on: ubuntu-latest - strategy: - matrix: - platform: - - target: x86_64 - - target: x86 - - target: aarch64 - - target: armv7 - - steps: - - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: 3.x - - - name: Build wheels - uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 - with: - target: ${{ matrix.platform.target }} - args: --release --out dist - manylinux: auto - - - name: Upload wheels - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: wheels-linux-${{ matrix.platform.target }} - path: dist - - musllinux: - name: build wheels (Linux Musl, ${{ matrix.platform.target }}) - runs-on: ubuntu-latest - strategy: - matrix: - platform: - - target: x86_64 - - target: x86 - - target: aarch64 - - target: armv7 - - steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: 3.x - - - name: Build wheels - uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 - with: - target: ${{ matrix.platform.target }} - args: --release --out dist - manylinux: musllinux_1_2 - - - name: Upload wheels - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: wheels-musllinux-${{ matrix.platform.target }} - path: dist - - windows: - name: build wheels (Windows, ${{ matrix.platform.target }}) - runs-on: windows-latest - strategy: - matrix: - platform: - - target: x64 - - target: x86 - - steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: 3.x - architecture: ${{ matrix.platform.target }} - - - name: Build wheels - uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 - with: - target: ${{ matrix.platform.target }} - args: --release --out dist - - - name: Upload wheels - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: wheels-windows-${{ matrix.platform.target }} - path: dist - - macos: - name: build wheels (macOS, ${{ matrix.platform.target }}) - runs-on: ${{ matrix.platform.runner }} - strategy: - matrix: - platform: - - runner: macos-15-intel - target: x86_64 - - runner: macos-15 - target: aarch64 - - steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: 3.x - - - name: Build wheels - uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 - with: - target: ${{ matrix.platform.target }} - args: --release --out dist - - - name: Upload wheels - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: wheels-macos-${{ matrix.platform.target }} - path: dist - - sdist: - name: build source distribution - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: 3.x - - - name: Build sdist - uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 - with: - command: sdist - args: --out dist - - - name: Upload sdist - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: wheels-sdist - path: dist - - release: - name: "release" - - runs-on: ubuntu-latest - - if: startsWith(github.ref, 'refs/tags/') - - needs: [linux, musllinux, windows, macos, sdist] - - steps: - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - - - name: Publish to PyPI - uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4 - env: - MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - with: - command: upload - args: --non-interactive --skip-existing wheels-*/* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f54bfee7..5b51c215 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -265,14 +265,6 @@ jobs: run: cargo codspeed run --bench ${{ matrix.project }} mode: walltime - build-binaries: - name: "build binaries" - - needs: determine_changes - if: ${{ (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - - uses: ./.github/workflows/build.yml - project-diff: name: "run project diff" diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 00000000..6a2794a9 --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,71 @@ +name: Deploy Documentation + +on: + workflow_dispatch: + inputs: + ref: + description: "The commit SHA, tag, or branch to publish. Uses the default branch if not specified." + default: "" + type: string + + workflow_call: + inputs: + plan: + required: true + type: string + +concurrency: + group: "pages" + cancel-in-progress: false + +permissions: {} + +env: + PYTHON_VERSION: "3.10" + +jobs: + build: + name: "Build docs" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + + - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Prepare docs + run: uv run --script scripts/prepare_docs.py + + - name: Build docs + run: uv run --isolated --with-requirements docs/requirements.txt zensical build + + - name: Upload artifact + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 + with: + path: ./site + + deploy: + runs-on: ubuntu-latest + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + permissions: + contents: read + pages: write + id-token: write + + needs: build + + if: github.ref == 'refs/heads/main' + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 00000000..f073130c --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,32 @@ +# Publish a release to PyPI. +# +# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job +# within `cargo-dist`. +name: "Publish to PyPI" + +on: + workflow_call: + inputs: + plan: + required: true + type: string + +jobs: + pypi-publish: + name: Upload to PyPI + runs-on: ubuntu-latest + environment: + name: release + permissions: + # For PyPI's trusted publishing. + id-token: write + steps: + - name: "Install uv" + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + pattern: wheels-* + path: wheels + merge-multiple: true + - name: Publish to PyPi + run: uv publish -v wheels/* diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 51238353..00000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Release Drafter and Labels - -on: - push: - branches: - - main - - pull_request: - types: [edited, opened, reopened, synchronize, unlabeled, labeled] - -permissions: - contents: read - -jobs: - update_release_draft: - permissions: - contents: write - pull-requests: write - - runs-on: ubuntu-latest - - steps: - - uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - require_label: - if: github.event.pull_request - - needs: [update_release_draft] - - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Wait for labels to be added - # Don't shout at the PR author right away - run: sleep 20 - - - uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5.1 - with: - mode: minimum - count: 1 - labels: ".+" - add_comment: true - use_regex: true - message: "Please add a label to this pull request." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07cb86d9..6a780be3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,68 +1,261 @@ +# 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 workflow_dispatch with a 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: - push: - tags: - - v[0-9]*.[0-9]*.[0-9]* - -env: - PYTHON_VERSION: "3.14" + pull_request: + workflow_dispatch: + inputs: + tag: + description: Release Tag + required: true + default: dry-run + type: string jobs: - release_github: - runs-on: ubuntu-latest + # 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: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }} + tag-flag: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }} + publishing: ${{ inputs.tag && inputs.tag != 'dry-run' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + 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.30.3/cargo-dist-installer.sh | sh" + - name: Cache dist + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 + 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 ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || '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@330a01c490aca151604b8cf639adc76d48f6c5d4 + with: + name: artifacts-plan-dist-manifest + path: plan-dist-manifest.json - permissions: - contents: write + custom-build-binaries: + needs: + - plan + if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }} + uses: ./.github/workflows/build-binaries.yml + with: + plan: ${{ needs.plan.outputs.val }} + secrets: inherit + # Build and package all the platform-agnostic(ish) things + build-global-artifacts: + needs: + - plan + - custom-build-binaries + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 with: persist-credentials: false - - - name: Publish Latest Draft + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 + 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@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: cargo-dist + shell: bash run: | - if gh release list | grep Draft; then - old_version="$(gh release list | grep Draft | head -1 | cut -f1)" - new_version="${GITHUB_REF_NAME}" - body=$(gh release view "$old_version" --json body -q ".body" | sed "s/\.\.\.$old_version/...$new_version/g") - gh release delete "$old_version" - gh release create "$new_version" --title "${GITHUB_REF_NAME}" --notes "$body"; - else - gh release create "$new_version" --title "${GITHUB_REF_NAME}"; - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json + echo "dist ran successfully" - publish-docs: - runs-on: ubuntu-latest + # 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" - name: Docs builder and publisher + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 + with: + name: artifacts-build-global + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + # Determines if we should publish/announce + host: + needs: + - plan + - custom-build-binaries + - 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.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.host.outputs.manifest }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 + 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@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce" + - 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@330a01c490aca151604b8cf639adc76d48f6c5d4 + with: + # Overwrite the previous copy + name: artifacts-dist-manifest + path: dist-manifest.json + custom-publish-pypi: + needs: + - plan + - host + if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} + uses: ./.github/workflows/publish-pypi.yml + with: + plan: ${{ needs.plan.outputs.val }} + secrets: inherit + # publish jobs get escalated permissions permissions: - contents: write + "id-token": "write" + "packages": "write" + # Create a GitHub Release while uploading all files to it + announce: + needs: + - plan + - host + - custom-publish-pypi + # 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' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') }} + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 with: persist-credentials: false - - - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + submodules: recursive + # Create a GitHub Release while uploading all files to it + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: - python-version: ${{ env.PYTHON_VERSION }} - enable-cache: false - - - name: Prepare docs - run: uv run --script scripts/prepare_docs.py + 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(needs.host.outputs.val).announcement_is_prerelease && '--prerelease' || '' }}" + ANNOUNCEMENT_TITLE: "${{ fromJson(needs.host.outputs.val).announcement_title }}" + ANNOUNCEMENT_BODY: "${{ fromJson(needs.host.outputs.val).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 - - name: Build docs - run: uv run --isolated --only-group docs zensical build + gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* - - name: Deploy - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: site - publish_branch: gh-pages - keep_files: false - force_orphan: true + custom-publish-docs: + needs: + - plan + - announce + uses: ./.github/workflows/publish-docs.yml + with: + plan: ${{ needs.plan.outputs.val }} + secrets: inherit + permissions: + "contents": "read" + "id-token": "write" + "pages": "write" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..825c32f0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/Cargo.lock b/Cargo.lock index 5993726b..185b2771 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1144,29 +1144,7 @@ dependencies = [ [[package]] name = "karva" -version = "0.1.11" -dependencies = [ - "karva_cli", - "karva_core", - "pyo3", -] - -[[package]] -name = "karva_benchmark" version = "0.0.0" -dependencies = [ - "anyhow", - "camino", - "codspeed-criterion-compat", - "codspeed-divan-compat", - "karva_core", - "karva_project", - "karva_projects", -] - -[[package]] -name = "karva_cli" -version = "0.1.11" dependencies = [ "anyhow", "argfile", @@ -1193,6 +1171,19 @@ dependencies = [ "wild", ] +[[package]] +name = "karva_benchmark" +version = "0.0.0" +dependencies = [ + "anyhow", + "camino", + "codspeed-criterion-compat", + "codspeed-divan-compat", + "karva_core", + "karva_project", + "karva_projects", +] + [[package]] name = "karva_combine" version = "0.0.0" @@ -1231,7 +1222,7 @@ dependencies = [ "camino", "clap", "itertools 0.14.0", - "karva_cli", + "karva", "karva_project", "markdown", "pretty_assertions", @@ -1295,6 +1286,14 @@ dependencies = [ "tracing", ] +[[package]] +name = "karva_python" +version = "0.0.0" +dependencies = [ + "karva_core", + "pyo3", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2725,7 +2724,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 06f4e98d..49c8fb7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,13 @@ authors = ["Matthew McKee "] license = "MIT" [workspace.dependencies] +karva= { path = "crates/karva" } +karva_combine = { path = "crates/karva_combine" } +karva_core = { path = "crates/karva_core" } +karva_macros = { path = "crates/karva_macros" } +karva_project = { path = "crates/karva_project" } +karva_projects = { path = "crates/karva_projects" } + anyhow = { version = "1.0.100" } argfile = { version = "0.2.1" } camino = { version = "1.2", features = ["serde1"] } @@ -30,12 +37,6 @@ ignore = { version = "0.4.25" } insta = { version = "1.44.3" } insta-cmd = { version = "0.6.0" } itertools = { version = "0.14.0" } -karva_cli = { path = "crates/karva_cli" } -karva_combine = { path = "crates/karva_combine" } -karva_core = { path = "crates/karva_core" } -karva_macros = { path = "crates/karva_macros" } -karva_project = { path = "crates/karva_project" } -karva_projects = { path = "crates/karva_projects" } markdown = { version = "1.0.0" } pretty_assertions = { version = "1.4.1" } proc-macro2 = { version = "1.0.79" } @@ -87,3 +88,8 @@ too_many_lines = "allow" significant_drop_tightening = "allow" must_use_candidate = "allow" option_if_let_else = "allow" + +# The profile that 'dist' will build with +[profile.dist] +inherits = "release" +lto = "thin" diff --git a/README.md b/README.md index 92c8c5a1..dd9521ff 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Karva (0.1.11) +# Karva (0.0.0) [![codecov](https://codecov.io/gh/MatthewMckee4/karva/graph/badge.svg?token=VELHBTE1L9)](https://codecov.io/gh/MatthewMckee4/karva) ![PyPI - Version](https://img.shields.io/pypi/v/karva) diff --git a/crates/karva/Cargo.toml b/crates/karva/Cargo.toml index 6ddc87c3..084a1229 100644 --- a/crates/karva/Cargo.toml +++ b/crates/karva/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "karva" -version = "0.1.11" +version = "0.0.0" +default-run = "karva" edition = { workspace = true } rust-version = { workspace = true } @@ -10,15 +11,36 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } -[lib] -name = "karva" -crate-type = ["rlib", "cdylib"] - [dependencies] -karva_cli = { workspace = true } +camino = { workspace = true } karva_core = { workspace = true } +karva_project = { workspace = true } -pyo3 = { workspace = true } +ctrlc = { workspace = true } +clap = { workspace = true, features = ["wrap_help", "string", "env"] } +anyhow = { workspace = true } +argfile = { workspace = true } +chrono = { workspace = true } +colored = { workspace = true } +tracing = { workspace = true, features = ["release_max_level_debug"] } +tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +tracing-flame = { workspace = true } +tracing-tree = { workspace = true } +wild = { workspace = true } +ruff_db = { workspace = true } + +[dev-dependencies] +insta = { workspace = true, features = ["filters"] } +insta-cmd = { workspace = true } +rstest = { workspace = true } +directories = { workspace = true } +tempfile = { workspace = true } +dunce = { workspace = true } +ruff_python_trivia = { workspace = true } +regex = { workspace = true } [lints] workspace = true + +[package.metadata.dist] +dist = true diff --git a/crates/karva_cli/src/args.rs b/crates/karva/src/args.rs similarity index 100% rename from crates/karva_cli/src/args.rs rename to crates/karva/src/args.rs diff --git a/crates/karva/src/lib.rs b/crates/karva/src/lib.rs index 1f3a4f78..f9dd27e5 100644 --- a/crates/karva/src/lib.rs +++ b/crates/karva/src/lib.rs @@ -1,26 +1,254 @@ -use karva_cli::karva_main; -use karva_core::init_module; -use pyo3::prelude::*; - -#[pyfunction] -pub(crate) fn karva_run() -> i32 { - karva_main(|args| { - let mut args: Vec<_> = args.into_iter().skip(1).collect(); - if !args.is_empty() { - if let Some(arg) = args.first() { - if arg.to_string_lossy() == "python" { - args.remove(0); +use std::{ + ffi::OsString, + fmt::Write, + io::{self}, + process::{ExitCode, Termination}, + time::Instant, +}; + +use anyhow::{Context, Result}; +use camino::Utf8PathBuf; +use clap::Parser; +use colored::Colorize; +use karva_core::{ + DummyReporter, Printer, Reporter, TestCaseReporter, TestRunResult, TestRunner, + utils::current_python_version, +}; +use karva_project::{ + Db, OsSystem, ProjectDatabase, ProjectMetadata, ProjectOptionsOverrides, System, + VerbosityLevel, absolute, +}; +use ruff_db::diagnostic::{DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics}; + +use crate::{ + args::{Command, TerminalColor, TestCommand}, + logging::setup_tracing, +}; + +mod args; +mod logging; +mod version; + +pub use args::Args; + +pub fn karva_main(f: impl FnOnce(Vec) -> Vec) -> ExitStatus { + run(f).unwrap_or_else(|error| { + use std::io::Write; + + let mut stderr = std::io::stderr().lock(); + + writeln!(stderr, "{}", "Karva failed".red().bold()).ok(); + for cause in error.chain() { + if let Some(ioerr) = cause.downcast_ref::() { + if ioerr.kind() == io::ErrorKind::BrokenPipe { + return ExitStatus::Success; } } + + writeln!(stderr, " {} {cause}", "Cause:".bold()).ok(); } - args + + ExitStatus::Error }) - .to_i32() } -#[pymodule] -pub(crate) fn _karva(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(karva_run, m)?)?; - init_module(py, m)?; +fn run(f: impl FnOnce(Vec) -> Vec) -> anyhow::Result { + let args = wild::args_os(); + + let args = f( + argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX) + .context("Failed to read CLI arguments from file")?, + ); + + let args = Args::parse_from(args); + + match args.command { + Command::Test(test_args) => test(test_args), + Command::Version => version().map(|()| ExitStatus::Success), + } +} + +pub(crate) fn version() -> Result<()> { + let mut stdout = Printer::default().stream_for_requested_summary().lock(); + if let Some(version_info) = crate::version::version() { + writeln!(stdout, "karva {}", &version_info)?; + } else { + writeln!(stdout, "Failed to get karva version")?; + } + Ok(()) } + +pub(crate) fn test(args: TestCommand) -> Result { + let verbosity = args.verbosity.level(); + + set_colored_override(args.color); + + let printer = Printer::new(verbosity, args.no_progress.unwrap_or(false)); + + let _guard = setup_tracing(verbosity); + + let cwd = { + let cwd = std::env::current_dir().context("Failed to get the current working directory")?; + Utf8PathBuf::from_path_buf(cwd) + .map_err(|path| { + anyhow::anyhow!( + "The current working directory `{}` contains non-Unicode characters. ty only supports Unicode paths.", + path.display() + ) + })? + }; + + let python_version = current_python_version(); + + let system = OsSystem::new(&cwd); + + let config_file = args.config_file.as_ref().map(|path| absolute(path, &cwd)); + + let mut project_metadata = match &config_file { + Some(config_file) => { + ProjectMetadata::from_config_file(config_file.clone(), &system, python_version)? + } + None => ProjectMetadata::discover(system.current_directory(), &system, python_version)?, + }; + + let project_options_overrides = ProjectOptionsOverrides::new(config_file, args.into_options()); + project_metadata.apply_overrides(&project_options_overrides); + + let mut db = ProjectDatabase::new(project_metadata, system)?; + + db.project_mut() + .set_verbose(verbosity >= VerbosityLevel::Verbose); + + // Listen to Ctrl+C and abort + ctrlc::set_handler(move || { + std::process::exit(0); + })?; + + let reporter: Box = if verbosity.is_quiet() { + Box::new(DummyReporter) + } else { + Box::new(TestCaseReporter::new(printer)) + }; + + let start_time = Instant::now(); + + let result = db.test_with_reporter(&*reporter); + + display_test_output(&db, &result, printer, start_time) +} + +#[derive(Copy, Clone)] +pub enum ExitStatus { + /// Checking was successful and there were no errors. + Success = 0, + + /// Checking was successful but there were errors. + Failure = 1, + + /// Checking failed. + Error = 2, +} + +impl Termination for ExitStatus { + fn report(self) -> ExitCode { + ExitCode::from(self as u8) + } +} + +impl ExitStatus { + pub const fn to_i32(self) -> i32 { + self as i32 + } +} + +fn set_colored_override(color: Option) { + let Some(color) = color else { + return; + }; + + match color { + TerminalColor::Auto => { + colored::control::unset_override(); + } + TerminalColor::Always => { + colored::control::set_override(true); + } + TerminalColor::Never => { + colored::control::set_override(false); + } + } +} + +fn display_test_output( + db: &dyn Db, + result: &TestRunResult, + printer: Printer, + start_time: Instant, +) -> Result { + let discovery_diagnostics = result.discovery_diagnostics(); + + let diagnostics = result.diagnostics(); + + let mut stdout = printer.stream_for_details().lock(); + + let diagnostic_format = db.project().settings().terminal().output_format.into(); + + let config = DisplayDiagnosticConfig::default() + .format(diagnostic_format) + .color(colored::control::SHOULD_COLORIZE.should_colorize()); + + let is_concise = matches!(diagnostic_format, DiagnosticFormat::Concise); + + if (!diagnostics.is_empty() || !discovery_diagnostics.is_empty()) + && result.stats().total() > 0 + && stdout.is_enabled() + { + writeln!(stdout)?; + } + + if !discovery_diagnostics.is_empty() && stdout.is_enabled() { + writeln!(stdout, "discovery diagnostics:")?; + writeln!(stdout)?; + write!( + stdout, + "{}", + DisplayDiagnostics::new(db, &config, discovery_diagnostics) + )?; + + if is_concise { + writeln!(stdout)?; + } + } + + if !diagnostics.is_empty() && stdout.is_enabled() { + writeln!(stdout, "diagnostics:")?; + writeln!(stdout)?; + write!( + stdout, + "{}", + DisplayDiagnostics::new(db, &config, diagnostics) + )?; + + if is_concise { + writeln!(stdout)?; + } + } + + if (diagnostics.is_empty() && discovery_diagnostics.is_empty()) + && result.stats().total() > 0 + && stdout.is_enabled() + { + writeln!(stdout)?; + } + + let mut result_stdout = printer.stream_for_failure_summary().lock(); + + write!(result_stdout, "{}", result.stats().display(start_time))?; + + if result.is_success() { + Ok(ExitStatus::Success) + } else { + Ok(ExitStatus::Failure) + } +} diff --git a/crates/karva_cli/src/logging.rs b/crates/karva/src/logging.rs similarity index 100% rename from crates/karva_cli/src/logging.rs rename to crates/karva/src/logging.rs diff --git a/crates/karva_cli/src/main.rs b/crates/karva/src/main.rs similarity index 58% rename from crates/karva_cli/src/main.rs rename to crates/karva/src/main.rs index 6580bbe7..ebd4ada0 100644 --- a/crates/karva_cli/src/main.rs +++ b/crates/karva/src/main.rs @@ -1,4 +1,4 @@ -use karva_cli::{ExitStatus, karva_main}; +use karva::{karva_main, ExitStatus}; fn main() -> ExitStatus { karva_main(|args| args) diff --git a/crates/karva_cli/src/version.rs b/crates/karva/src/version.rs similarity index 100% rename from crates/karva_cli/src/version.rs rename to crates/karva/src/version.rs diff --git a/crates/karva_cli/tests/it/basic.rs b/crates/karva/tests/it/basic.rs similarity index 100% rename from crates/karva_cli/tests/it/basic.rs rename to crates/karva/tests/it/basic.rs diff --git a/crates/karva_cli/tests/it/common/mod.rs b/crates/karva/tests/it/common/mod.rs similarity index 100% rename from crates/karva_cli/tests/it/common/mod.rs rename to crates/karva/tests/it/common/mod.rs diff --git a/crates/karva_cli/tests/it/configuration/mod.rs b/crates/karva/tests/it/configuration/mod.rs similarity index 100% rename from crates/karva_cli/tests/it/configuration/mod.rs rename to crates/karva/tests/it/configuration/mod.rs diff --git a/crates/karva_cli/tests/it/discovery/mod.rs b/crates/karva/tests/it/discovery/mod.rs similarity index 100% rename from crates/karva_cli/tests/it/discovery/mod.rs rename to crates/karva/tests/it/discovery/mod.rs diff --git a/crates/karva_cli/tests/it/discovery/nested_layouts.rs b/crates/karva/tests/it/discovery/nested_layouts.rs similarity index 100% rename from crates/karva_cli/tests/it/discovery/nested_layouts.rs rename to crates/karva/tests/it/discovery/nested_layouts.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/autouse.rs b/crates/karva/tests/it/extensions/fixtures/autouse.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/autouse.rs rename to crates/karva/tests/it/extensions/fixtures/autouse.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/basic.rs b/crates/karva/tests/it/extensions/fixtures/basic.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/basic.rs rename to crates/karva/tests/it/extensions/fixtures/basic.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/builtins.rs b/crates/karva/tests/it/extensions/fixtures/builtins.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/builtins.rs rename to crates/karva/tests/it/extensions/fixtures/builtins.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/decorators.rs b/crates/karva/tests/it/extensions/fixtures/decorators.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/decorators.rs rename to crates/karva/tests/it/extensions/fixtures/decorators.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/generators.rs b/crates/karva/tests/it/extensions/fixtures/generators.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/generators.rs rename to crates/karva/tests/it/extensions/fixtures/generators.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/invalid.rs b/crates/karva/tests/it/extensions/fixtures/invalid.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/invalid.rs rename to crates/karva/tests/it/extensions/fixtures/invalid.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/mod.rs b/crates/karva/tests/it/extensions/fixtures/mod.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/mod.rs rename to crates/karva/tests/it/extensions/fixtures/mod.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/parametrized.rs b/crates/karva/tests/it/extensions/fixtures/parametrized.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/parametrized.rs rename to crates/karva/tests/it/extensions/fixtures/parametrized.rs diff --git a/crates/karva_cli/tests/it/extensions/fixtures/request.rs b/crates/karva/tests/it/extensions/fixtures/request.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/fixtures/request.rs rename to crates/karva/tests/it/extensions/fixtures/request.rs diff --git a/crates/karva_cli/tests/it/extensions/functions.rs b/crates/karva/tests/it/extensions/functions.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/functions.rs rename to crates/karva/tests/it/extensions/functions.rs diff --git a/crates/karva_cli/tests/it/extensions/mod.rs b/crates/karva/tests/it/extensions/mod.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/mod.rs rename to crates/karva/tests/it/extensions/mod.rs diff --git a/crates/karva_cli/tests/it/extensions/tags/expect_fail.rs b/crates/karva/tests/it/extensions/tags/expect_fail.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/tags/expect_fail.rs rename to crates/karva/tests/it/extensions/tags/expect_fail.rs diff --git a/crates/karva_cli/tests/it/extensions/tags/mod.rs b/crates/karva/tests/it/extensions/tags/mod.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/tags/mod.rs rename to crates/karva/tests/it/extensions/tags/mod.rs diff --git a/crates/karva_cli/tests/it/extensions/tags/parametrize.rs b/crates/karva/tests/it/extensions/tags/parametrize.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/tags/parametrize.rs rename to crates/karva/tests/it/extensions/tags/parametrize.rs diff --git a/crates/karva_cli/tests/it/extensions/tags/skip.rs b/crates/karva/tests/it/extensions/tags/skip.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/tags/skip.rs rename to crates/karva/tests/it/extensions/tags/skip.rs diff --git a/crates/karva_cli/tests/it/extensions/tags/use_fixtures.rs b/crates/karva/tests/it/extensions/tags/use_fixtures.rs similarity index 100% rename from crates/karva_cli/tests/it/extensions/tags/use_fixtures.rs rename to crates/karva/tests/it/extensions/tags/use_fixtures.rs diff --git a/crates/karva_cli/tests/it/main.rs b/crates/karva/tests/it/main.rs similarity index 100% rename from crates/karva_cli/tests/it/main.rs rename to crates/karva/tests/it/main.rs diff --git a/crates/karva_cli/Cargo.toml b/crates/karva_cli/Cargo.toml deleted file mode 100644 index 67b2f78d..00000000 --- a/crates/karva_cli/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "karva_cli" -version = "0.1.11" -default-run = "karva" - -edition = { workspace = true } -rust-version = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -repository = { workspace = true } -authors = { workspace = true } -license = { workspace = true } - -[[bin]] -name = "karva" -path = "src/main.rs" - -[dependencies] -camino = { workspace = true } -karva_core = { workspace = true } -karva_project = { workspace = true } - -ctrlc = { workspace = true } -clap = { workspace = true, features = ["wrap_help", "string", "env"] } -anyhow = { workspace = true } -argfile = { workspace = true } -chrono = { workspace = true } -colored = { workspace = true } -tracing = { workspace = true, features = ["release_max_level_debug"] } -tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } -tracing-flame = { workspace = true } -tracing-tree = { workspace = true } -wild = { workspace = true } -ruff_db = { workspace = true } - -[dev-dependencies] -insta = { workspace = true, features = ["filters"] } -insta-cmd = { workspace = true } -rstest = { workspace = true } -directories = { workspace = true } -tempfile = { workspace = true } -dunce = { workspace = true } -ruff_python_trivia = { workspace = true } -regex = { workspace = true } - -[lints] -workspace = true diff --git a/crates/karva_cli/src/lib.rs b/crates/karva_cli/src/lib.rs deleted file mode 100644 index f9dd27e5..00000000 --- a/crates/karva_cli/src/lib.rs +++ /dev/null @@ -1,254 +0,0 @@ -use std::{ - ffi::OsString, - fmt::Write, - io::{self}, - process::{ExitCode, Termination}, - time::Instant, -}; - -use anyhow::{Context, Result}; -use camino::Utf8PathBuf; -use clap::Parser; -use colored::Colorize; -use karva_core::{ - DummyReporter, Printer, Reporter, TestCaseReporter, TestRunResult, TestRunner, - utils::current_python_version, -}; -use karva_project::{ - Db, OsSystem, ProjectDatabase, ProjectMetadata, ProjectOptionsOverrides, System, - VerbosityLevel, absolute, -}; -use ruff_db::diagnostic::{DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics}; - -use crate::{ - args::{Command, TerminalColor, TestCommand}, - logging::setup_tracing, -}; - -mod args; -mod logging; -mod version; - -pub use args::Args; - -pub fn karva_main(f: impl FnOnce(Vec) -> Vec) -> ExitStatus { - run(f).unwrap_or_else(|error| { - use std::io::Write; - - let mut stderr = std::io::stderr().lock(); - - writeln!(stderr, "{}", "Karva failed".red().bold()).ok(); - for cause in error.chain() { - if let Some(ioerr) = cause.downcast_ref::() { - if ioerr.kind() == io::ErrorKind::BrokenPipe { - return ExitStatus::Success; - } - } - - writeln!(stderr, " {} {cause}", "Cause:".bold()).ok(); - } - - ExitStatus::Error - }) -} - -fn run(f: impl FnOnce(Vec) -> Vec) -> anyhow::Result { - let args = wild::args_os(); - - let args = f( - argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX) - .context("Failed to read CLI arguments from file")?, - ); - - let args = Args::parse_from(args); - - match args.command { - Command::Test(test_args) => test(test_args), - Command::Version => version().map(|()| ExitStatus::Success), - } -} - -pub(crate) fn version() -> Result<()> { - let mut stdout = Printer::default().stream_for_requested_summary().lock(); - if let Some(version_info) = crate::version::version() { - writeln!(stdout, "karva {}", &version_info)?; - } else { - writeln!(stdout, "Failed to get karva version")?; - } - - Ok(()) -} - -pub(crate) fn test(args: TestCommand) -> Result { - let verbosity = args.verbosity.level(); - - set_colored_override(args.color); - - let printer = Printer::new(verbosity, args.no_progress.unwrap_or(false)); - - let _guard = setup_tracing(verbosity); - - let cwd = { - let cwd = std::env::current_dir().context("Failed to get the current working directory")?; - Utf8PathBuf::from_path_buf(cwd) - .map_err(|path| { - anyhow::anyhow!( - "The current working directory `{}` contains non-Unicode characters. ty only supports Unicode paths.", - path.display() - ) - })? - }; - - let python_version = current_python_version(); - - let system = OsSystem::new(&cwd); - - let config_file = args.config_file.as_ref().map(|path| absolute(path, &cwd)); - - let mut project_metadata = match &config_file { - Some(config_file) => { - ProjectMetadata::from_config_file(config_file.clone(), &system, python_version)? - } - None => ProjectMetadata::discover(system.current_directory(), &system, python_version)?, - }; - - let project_options_overrides = ProjectOptionsOverrides::new(config_file, args.into_options()); - project_metadata.apply_overrides(&project_options_overrides); - - let mut db = ProjectDatabase::new(project_metadata, system)?; - - db.project_mut() - .set_verbose(verbosity >= VerbosityLevel::Verbose); - - // Listen to Ctrl+C and abort - ctrlc::set_handler(move || { - std::process::exit(0); - })?; - - let reporter: Box = if verbosity.is_quiet() { - Box::new(DummyReporter) - } else { - Box::new(TestCaseReporter::new(printer)) - }; - - let start_time = Instant::now(); - - let result = db.test_with_reporter(&*reporter); - - display_test_output(&db, &result, printer, start_time) -} - -#[derive(Copy, Clone)] -pub enum ExitStatus { - /// Checking was successful and there were no errors. - Success = 0, - - /// Checking was successful but there were errors. - Failure = 1, - - /// Checking failed. - Error = 2, -} - -impl Termination for ExitStatus { - fn report(self) -> ExitCode { - ExitCode::from(self as u8) - } -} - -impl ExitStatus { - pub const fn to_i32(self) -> i32 { - self as i32 - } -} - -fn set_colored_override(color: Option) { - let Some(color) = color else { - return; - }; - - match color { - TerminalColor::Auto => { - colored::control::unset_override(); - } - TerminalColor::Always => { - colored::control::set_override(true); - } - TerminalColor::Never => { - colored::control::set_override(false); - } - } -} - -fn display_test_output( - db: &dyn Db, - result: &TestRunResult, - printer: Printer, - start_time: Instant, -) -> Result { - let discovery_diagnostics = result.discovery_diagnostics(); - - let diagnostics = result.diagnostics(); - - let mut stdout = printer.stream_for_details().lock(); - - let diagnostic_format = db.project().settings().terminal().output_format.into(); - - let config = DisplayDiagnosticConfig::default() - .format(diagnostic_format) - .color(colored::control::SHOULD_COLORIZE.should_colorize()); - - let is_concise = matches!(diagnostic_format, DiagnosticFormat::Concise); - - if (!diagnostics.is_empty() || !discovery_diagnostics.is_empty()) - && result.stats().total() > 0 - && stdout.is_enabled() - { - writeln!(stdout)?; - } - - if !discovery_diagnostics.is_empty() && stdout.is_enabled() { - writeln!(stdout, "discovery diagnostics:")?; - writeln!(stdout)?; - write!( - stdout, - "{}", - DisplayDiagnostics::new(db, &config, discovery_diagnostics) - )?; - - if is_concise { - writeln!(stdout)?; - } - } - - if !diagnostics.is_empty() && stdout.is_enabled() { - writeln!(stdout, "diagnostics:")?; - writeln!(stdout)?; - write!( - stdout, - "{}", - DisplayDiagnostics::new(db, &config, diagnostics) - )?; - - if is_concise { - writeln!(stdout)?; - } - } - - if (diagnostics.is_empty() && discovery_diagnostics.is_empty()) - && result.stats().total() > 0 - && stdout.is_enabled() - { - writeln!(stdout)?; - } - - let mut result_stdout = printer.stream_for_failure_summary().lock(); - - write!(result_stdout, "{}", result.stats().display(start_time))?; - - if result.is_success() { - Ok(ExitStatus::Success) - } else { - Ok(ExitStatus::Failure) - } -} diff --git a/crates/karva_dev/Cargo.toml b/crates/karva_dev/Cargo.toml index ef7cded1..e5ec48b3 100644 --- a/crates/karva_dev/Cargo.toml +++ b/crates/karva_dev/Cargo.toml @@ -11,7 +11,7 @@ repository = { workspace = true } license = { workspace = true } [dependencies] -karva_cli = { workspace = true } +karva = { workspace = true } ruff_options_metadata = { workspace = true } anyhow = { workspace = true } diff --git a/crates/karva_dev/src/generate_cli_reference.rs b/crates/karva_dev/src/generate_cli_reference.rs index ead0d514..ee5f7d52 100644 --- a/crates/karva_dev/src/generate_cli_reference.rs +++ b/crates/karva_dev/src/generate_cli_reference.rs @@ -1,11 +1,11 @@ //! Generate a Markdown-compatible reference for the karva command-line interface. use std::cmp::max; -use anyhow::{Result, bail}; +use anyhow::{bail, Result}; use camino::Utf8PathBuf; use clap::{Command, CommandFactory}; use itertools::Itertools; -use karva_cli::Args as Cli; +use karva::Args as Cli; use pretty_assertions::StrComparison; use crate::{Mode, REGENERATE_ALL_COMMAND, ROOT_DIR}; @@ -80,7 +80,7 @@ fn generate() -> String { let mut parents = Vec::new(); - output.push_str("\n\n"); + output.push_str("\n\n"); output.push_str("# CLI Reference\n\n"); generate_command(&mut output, &karva, &mut parents); @@ -324,7 +324,7 @@ mod tests { use anyhow::Result; - use super::{Args, Mode, main}; + use super::{main, Args, Mode}; #[test] #[cfg(unix)] diff --git a/crates/karva_python/Cargo.toml b/crates/karva_python/Cargo.toml new file mode 100644 index 00000000..78d58781 --- /dev/null +++ b/crates/karva_python/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "karva_python" +version = "0.0.0" + +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +license = { workspace = true } + +[lib] +name = "karva_python" +crate-type = ["rlib", "cdylib"] + +[dependencies] +karva_core = { workspace = true } + +pyo3 = { workspace = true } + +[lints] +workspace = true diff --git a/crates/karva_python/src/lib.rs b/crates/karva_python/src/lib.rs new file mode 100644 index 00000000..b1507250 --- /dev/null +++ b/crates/karva_python/src/lib.rs @@ -0,0 +1,8 @@ +use karva_core::init_module; +use pyo3::prelude::*; + +#[pymodule] +pub(crate) fn _karva(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { + init_module(py, m)?; + Ok(()) +} diff --git a/dist-workspace.toml b/dist-workspace.toml new file mode 100644 index 00000000..4a32dc2a --- /dev/null +++ b/dist-workspace.toml @@ -0,0 +1,50 @@ +[workspace] +members = ["cargo:."] +version = "0.0.0" + +# Config for 'dist' +[dist] +# The preferred dist version to use in CI (Cargo.toml SemVer syntax) +cargo-dist-version = "0.30.3" +# Whether to consider the binaries in a package for distribution (defaults true) +dist = false +# CI backends to support +ci = "github" +# The installers to generate for each app +installers = ["shell", "powershell"] +# The archive format to use for windows builds (defaults .zip) +windows-archive = ".zip" +# The archive format to use for non-windows builds (defaults .tar.xz) +unix-archive = ".tar.gz" +# Target platforms to build apps for (Rust target-triple syntax) +targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "aarch64-pc-windows-msvc", "arm-unknown-linux-musleabihf", "armv7-unknown-linux-gnueabihf", "armv7-unknown-linux-musleabihf", "x86_64-apple-darwin", "powerpc64-unknown-linux-gnu", "powerpc64le-unknown-linux-gnu", "riscv64gc-unknown-linux-gnu", "s390x-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "i686-unknown-linux-musl", "i686-pc-windows-msvc"] +# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true) +auto-includes = false +# Whether dist should create a Github Release or use an existing draft +create-release = true +# Which actions to run on pull requests +pr-run-mode = "plan" +# Whether CI should trigger releases with dispatches instead of tag pushes +dispatch-releases = true +# Which phase dist should use to create the GitHub release +github-release = "announce" +# Whether CI should include auto-generated code to build local artifacts +build-local-artifacts = false +# Local artifacts jobs to run in CI +local-artifacts-jobs = ["./build-binaries"] +# Publish jobs to run in CI +publish-jobs = ["./publish-pypi"] +# Post-announce jobs to run in CI +post-announce-jobs = ["./publish-docs"] +# Custom permissions for GitHub Jobs +github-custom-job-permissions = { "publish-docs" = { contents = "read", pages = "write", id-token = "write" }} +# Whether to install an updater program +install-updater = false +# Path that installers should place binaries in +install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"] + +[dist.github-action-commits] +"actions/checkout" = "8e8c483db84b4bee98b60c0593521ed34d9990e8" # v6.0.1 +"actions/upload-artifact" = "330a01c490aca151604b8cf639adc76d48f6c5d4" # v5.0.0 +"actions/download-artifact" = "018cc2cf5baa6db3ef3c5f8a56943fffe632ef53" # v6.0.0 +"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3 diff --git a/docs/cli.md b/docs/cli.md index 143eefe9..30fc9c8e 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,4 +1,4 @@ - + # CLI Reference @@ -82,4 +82,3 @@ Print this message or the help of the given subcommand(s) ``` karva help [COMMAND] ``` - diff --git a/pyproject.toml b/pyproject.toml index 165ff29c..399d7d1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ["maturin>=1.7,<2.0"] [project] name = "karva" -version = "0.1.11" +version = "0.0.0" description = "A Python test framework, written in Rust." authors = [{ name = "Matthew McKee", email = "matthewmckee4@yahoo.co.uk" }] license = { file = "LICENSE" } @@ -22,9 +22,6 @@ classifiers = [ "Programming Language :: Rust", ] -[project.scripts] -karva = "karva._karva:karva_run" - [dependency-groups] dev = ["pre-commit", "ruff"] docs = ["zensical==0.0.10"] @@ -37,7 +34,7 @@ Issues = "https://github.com/MatthewMckee4/karva/issues" Repository = "https://github.com/MatthewMckee4/karva" [tool.maturin] -manifest-path = "crates/karva/Cargo.toml" +manifest-path = "crates/karva_python/Cargo.toml" module-name = "karva._karva" python-source = "python" features = ["pyo3/extension-module"] @@ -100,38 +97,3 @@ unfixable = [ "PLW1510", # automatic fix might obscure issue "RUF017", # Ruff's fix is faster, but I prefer using itertools.chain_from_iterable ] - -[tool.tbump] - -[[tool.tbump.before_commit]] -cmd = "cargo update -p karva" -name = "Update Cargo.lock" - -[[tool.tbump.file]] -src = "pyproject.toml" - -[[tool.tbump.file]] -src = "README.md" - -[[tool.tbump.file]] -src = "crates/karva/Cargo.toml" - -[[tool.tbump.file]] -src = "crates/karva_cli/Cargo.toml" - -[[tool.tbump.file]] -src = "python/karva/__init__.py" - -[tool.tbump.git] -message_template = "Bump to v{new_version}" -tag_template = "v{new_version}" - -[tool.tbump.version] -current = "0.1.11" -regex = ''' - (?P\d+) - \. - (?P\d+) - \. - (?P\d+) - ''' diff --git a/python/karva/__init__.py b/python/karva/__init__.py index d0344196..2d90b099 100644 --- a/python/karva/__init__.py +++ b/python/karva/__init__.py @@ -7,13 +7,12 @@ SkipError, fail, fixture, - karva_run, param, skip, tags, ) -__version__ = "0.1.11" +__version__ = "0.0.0" __all__: list[str] = [ "FailError", @@ -22,7 +21,6 @@ "SkipError", "fail", "fixture", - "karva_run", "param", "skip", "tags", diff --git a/python/karva/__main__.py b/python/karva/__main__.py deleted file mode 100644 index 92ed8bca..00000000 --- a/python/karva/__main__.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Karva is a Python test runner, written in Rust.""" - -from __future__ import annotations - -import os -import sys -import sysconfig -from pathlib import Path - -MAX_PATH_PARTS = 3 - - -def find_karva_bin() -> str: - """Return the karva binary path.""" - karva_exe = "karva" + sysconfig.get_config_var("EXE") - - scripts_path = Path(sysconfig.get_path("scripts")) / karva_exe - if scripts_path.is_file(): - return str(scripts_path) - - if sys.version_info >= (3, 10): - user_scheme = sysconfig.get_preferred_scheme("user") - elif os.name == "nt": - user_scheme = "nt_user" - elif sys.platform == "darwin" and sys._framework: - user_scheme = "osx_framework_user" - else: - user_scheme = "posix_user" - - user_path = Path(sysconfig.get_path("scripts", scheme=user_scheme)) / karva_exe - if user_path.is_file(): - return str(user_path) - - # Search in `bin` adjacent to package root (as created by `pip install --target`). - pkg_root = Path(__file__).parent.parent - target_path = pkg_root / "bin" / karva_exe - if target_path.is_file(): - return str(target_path) - - paths = os.environ.get("PATH", "").split(os.pathsep) - if len(paths) >= 2: - - def get_last_three_path_parts(path: str) -> list[str]: - """Return a list of up to the last three parts of a path.""" - parts: list[str] = [] - - while len(parts) < MAX_PATH_PARTS: - head, tail = os.path.split(path) - if tail or head != path: - parts.append(tail) - path = head - else: - parts.append(path) - break - - return parts - - maybe_overlay = get_last_three_path_parts(paths[0]) - maybe_normal = get_last_three_path_parts(paths[1]) - if ( - len(maybe_normal) >= MAX_PATH_PARTS - and maybe_normal[-1].startswith("pip-build-env-") - and maybe_normal[-2] == "normal" - and len(maybe_overlay) >= MAX_PATH_PARTS - and maybe_overlay[-1].startswith("pip-build-env-") - and maybe_overlay[-2] == "overlay" - ): - # The overlay must contain the karva binary. - candidate = Path(paths[0]) / karva_exe - if candidate.is_file(): - return str(candidate) - - raise FileNotFoundError(scripts_path) - - -if __name__ == "__main__": - karva = os.fsdecode(find_karva_bin()) - if sys.platform == "win32": - import subprocess - - completed_process = subprocess.run([karva, *sys.argv[1:]], check=True) # noqa: S603 - sys.exit(completed_process.returncode) - else: - os.execvp(karva, [karva, *sys.argv[1:]]) # noqa: S606 diff --git a/python/karva/_karva/__init__.pyi b/python/karva/_karva/__init__.pyi index fdf1d18d..ed986f4a 100644 --- a/python/karva/_karva/__init__.pyi +++ b/python/karva/_karva/__init__.pyi @@ -8,8 +8,6 @@ _ScopeName: TypeAlias = Literal["session", "package", "module", "function"] _T = TypeVar("_T") _P = ParamSpec("_P") -def karva_run() -> int: ... - class FixtureFunctionMarker(Generic[_P, _T]): def __call__( self, diff --git a/seal.toml b/seal.toml new file mode 100644 index 00000000..bb00c847 --- /dev/null +++ b/seal.toml @@ -0,0 +1,31 @@ +[release] +current-version = "0.0.0" + +version-files = [ + "README.md", + "crates/karva/Cargo.toml", + "crates/karva_python/Cargo.toml", + "pyproject.toml", + "python/karva/__init__.py", + { path = "dist-workspace.toml", field = "workspace.version", format = "toml" } +] + +commit-message = "Release v{version}" +branch-name = "release/v{version}" +push = true +confirm = true + +[changelog.section-labels] +"Bug Fixes" = ["bug"] +"Reporting" = ["reporting"] +"Extensions" = ["extensions/fixtures", "extensions/tags"] +"Configuration" = ["configuration"] +"Discovery" = ["discovery"] +"CLI" = ["cli"] +"Documentation" = ["documentation"] + +[changelog] +ignore-contributors = ["dependabot[bot]"] +ignore-labels = ["internal", "ci", "duplicate", "rust", "wontfix", "needs-decision"] +include-contributors = true +changelog-heading = "{version}"