Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

defaults:
run:
shell: bash

jobs:
test:
name: Build and test
runs-on: ubuntu-24.04
env:
IMPORT_NAME: src_py_lib
PYTHON_VERSION: "3.13"
UV_VERSION: "0.11.7"

steps:
- name: Check out code
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip

- name: Install uv
run: |
python -m pip install --upgrade pip
python -m pip install "uv==${UV_VERSION}"

- name: Validate lockfile
run: uv lock --check

- name: Lint Markdown
run: npx --yes markdownlint-cli2

- name: Lint Python
run: uv run --frozen ruff check .

- name: Check Python formatting
run: uv run --frozen ruff format --check .

- name: Type check
run: uv run --frozen pyright

- name: Run tests
run: uv run --frozen python -m unittest discover -s tests

- name: Smoke test source checkout import
run: |
uv run --frozen python - <<'PY'
import os

import src_py_lib

if src_py_lib.__name__ != os.environ["IMPORT_NAME"]:
raise SystemExit(f"unexpected import name: {src_py_lib.__name__}")
PY

- name: Build wheel
run: uv build --wheel --out-dir dist --no-create-gitignore

- name: Smoke test installed wheel
run: |
python -m venv build/ci-venv
. build/ci-venv/bin/activate
python -m pip install --upgrade pip
python -m pip install dist/*.whl
python - <<'PY'
import os

import src_py_lib

if src_py_lib.__name__ != os.environ["IMPORT_NAME"]:
raise SystemExit(f"unexpected import name: {src_py_lib.__name__}")
PY

- name: Upload wheel artifact
uses: actions/upload-artifact@v7
with:
name: src-py-lib-wheel
path: dist/*.whl
190 changes: 190 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
name: Build release

on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Existing release tag to publish, for example v0.1.0"
required: true
type: string

permissions:
contents: write

concurrency:
group: release-${{ github.event.inputs.tag || github.ref_name }}
cancel-in-progress: false

defaults:
run:
shell: bash

jobs:
wheel:
name: Build wheel
runs-on: ubuntu-24.04
env:
IMPORT_NAME: src_py_lib
PYTHON_VERSION: "3.13"
UV_VERSION: "0.11.7"

steps:
- name: Check out release ref
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.event.inputs.tag || github.ref }}

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip

- name: Install build tools
run: |
python -m pip install --upgrade pip
python -m pip install "uv==${UV_VERSION}"

- name: Validate release inputs
id: release
run: |
release_tag="${{ github.event.inputs.tag || github.ref_name }}"
if [[ ! "${release_tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error title=Invalid release tag::Use a vMAJOR.MINOR.PATCH tag, got '${release_tag}'."
exit 1
fi
if ! git rev-parse --verify --quiet "refs/tags/${release_tag}" >/dev/null; then
echo "::error title=Missing tag::Tag '${release_tag}' was not fetched. Create and push it before running this workflow."
exit 1
fi

project_version=$(uv run --frozen python - <<'PY'
import tomllib

with open("pyproject.toml", "rb") as pyproject_file:
print(tomllib.load(pyproject_file)["project"]["version"])
PY
)
if [[ "v${project_version}" != "${release_tag}" ]]; then
echo "::error title=Version mismatch::pyproject.toml version '${project_version}' does not match tag '${release_tag}'."
exit 1
fi

echo "tag=${release_tag}" >> "${GITHUB_OUTPUT}"

- name: Validate package
run: |
uv lock --check
uv run --frozen ruff check .
uv run --frozen ruff format --check .
uv run --frozen pyright
uv run --frozen python -m unittest discover -s tests
uv run --frozen python - <<'PY'
import os

import src_py_lib

if src_py_lib.__name__ != os.environ["IMPORT_NAME"]:
raise SystemExit(f"unexpected import name: {src_py_lib.__name__}")
PY

- name: Build wheel
id: build
run: |
dist_dir="build/release/dist"
rm -rf build/release
mkdir -p "${dist_dir}"

uv build --wheel --out-dir "${dist_dir}" --no-create-gitignore
project_wheels=("${dist_dir}"/*.whl)
if [[ "${#project_wheels[@]}" -ne 1 ]]; then
echo "::error title=Unexpected wheel count::Expected one project wheel, found ${#project_wheels[@]}."
exit 1
fi
wheel_path="${project_wheels[0]}"
wheel_name="$(basename "${wheel_path}")"
checksum_path="${wheel_path}.sha256"

(
cd "$(dirname "${wheel_path}")"
shasum -a 256 "${wheel_name}" > "$(basename "${checksum_path}")"
)

echo "wheel_path=${wheel_path}" >> "${GITHUB_OUTPUT}"
echo "wheel_name=${wheel_name}" >> "${GITHUB_OUTPUT}"
echo "checksum_path=${checksum_path}" >> "${GITHUB_OUTPUT}"

- name: Smoke test installed wheel
run: |
python -m venv build/release/install-venv
. build/release/install-venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "${{ steps.build.outputs.wheel_path }}"
python - <<'PY'
import os

import src_py_lib

if src_py_lib.__name__ != os.environ["IMPORT_NAME"]:
raise SystemExit(f"unexpected import name: {src_py_lib.__name__}")
PY

- name: Write release notes
id: notes
run: |
release_tag="${{ steps.release.outputs.tag }}"
wheel_name="${{ steps.build.outputs.wheel_name }}"
notes_path="build/release/release-notes.md"
cat > "${notes_path}" <<EOF
## Install

Install from the release wheel:

\`\`\`sh
pip install "https://github.com/sourcegraph/src-py-lib/releases/download/${release_tag}/${wheel_name}"
\`\`\`

Or install this tag with uv:

\`\`\`sh
uv add "git+https://github.com/sourcegraph/src-py-lib.git@${release_tag}"
\`\`\`

Verify downloaded wheel assets with the matching \`.sha256\` file.
EOF
echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"

- name: Upload workflow artifact
uses: actions/upload-artifact@v7
with:
name: src-py-lib-release
path: |
${{ steps.build.outputs.wheel_path }}
${{ steps.build.outputs.checksum_path }}
${{ steps.notes.outputs.path }}

- name: Publish GitHub release assets
env:
GH_TOKEN: ${{ github.token }}
run: |
release_tag="${{ steps.release.outputs.tag }}"
wheel_path="${{ steps.build.outputs.wheel_path }}"
checksum_path="${{ steps.build.outputs.checksum_path }}"
notes_path="${{ steps.notes.outputs.path }}"

if gh release view "${release_tag}" >/dev/null 2>&1; then
gh release edit "${release_tag}" --title "${release_tag}" --notes-file "${notes_path}"
gh release upload "${release_tag}" "${wheel_path}" "${checksum_path}" --clobber
else
gh release create "${release_tag}" \
"${wheel_path}" \
"${checksum_path}" \
--title "${release_tag}" \
--notes-file "${notes_path}" \
--verify-tag
fi