Skip to content

docs: clarify Sourcegraph endpoint/token are required, recommend env … #25

docs: clarify Sourcegraph endpoint/token are required, recommend env …

docs: clarify Sourcegraph endpoint/token are required, recommend env … #25

Workflow file for this run

name: Build release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
version:
description: "Package version to publish, for example 0.1.0 or v0.1.0"
required: true
type: string
permissions:
contents: read
pull-requests: read
concurrency:
group: release-${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
cancel-in-progress: false
defaults:
run:
shell: bash
jobs:
release_ref:
name: Resolve release tag
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.release.outputs.tag }}
version: ${{ steps.release.outputs.version }}
steps:
- name: Resolve release tag
id: release
env:
RELEASE_INPUT: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
run: |
release_input="${RELEASE_INPUT}"
if [[ ! "${release_input}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error title=Invalid release version::Use MAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH, got '${release_input}'."
exit 1
fi
release_version="${release_input#v}"
release_tag="v${release_version}"
echo "tag=${release_tag}" >> "${GITHUB_OUTPUT}"
echo "version=${release_version}" >> "${GITHUB_OUTPUT}"
validate:
name: Validate
needs: release_ref
uses: ./.github/workflows/validate.yml
with:
ref: ${{ needs.release_ref.outputs.tag }}
build-package: false
wheel:
name: Build wheel
needs: release_ref
runs-on: ubuntu-24.04
env:
IMPORT_NAME: src_py_lib
PYTHON_VERSION: "3.11"
UV_VERSION: "0.11.7"
steps:
- name: Check out release ref
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ needs.release_ref.outputs.tag }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Cache uv
uses: actions/cache@v6
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('uv.lock') }}
restore-keys: |
uv-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-
- name: Install build tools
run: python -m pip install "uv==${UV_VERSION}"
- name: Validate release inputs
id: release
env:
RELEASE_TAG: ${{ needs.release_ref.outputs.tag }}
run: |
release_tag="${RELEASE_TAG}"
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
tag_revision="$(git rev-list -n 1 "${release_tag}")"
git fetch --no-tags origin main
main_revision="$(git rev-parse origin/main)"
if ! git merge-base --is-ancestor "${tag_revision}" "${main_revision}"; then
echo "::error title=Tag is not on main::Tag '${release_tag}' points at ${tag_revision}, which is not reachable from origin/main."
echo "::error::Tag a commit from main, then rerun the release."
exit 1
fi
echo "tag=${release_tag}" >> "${GITHUB_OUTPUT}"
echo "version=${release_tag#v}" >> "${GITHUB_OUTPUT}"
- name: Validate runtime dependencies
run: |
runtime_requirements_file="build/release/runtime-requirements.txt"
mkdir -p "$(dirname "${runtime_requirements_file}")"
uv export \
--no-dev \
--no-emit-project \
--no-hashes \
--no-header \
--no-annotate \
--frozen \
--output-file "${runtime_requirements_file}"
if grep -Eq '(^-e[[:space:]]|^(\.\.?/)|(^|[[:space:]])file:| @ (\.\.?/|file:))' "${runtime_requirements_file}"; then
echo "::error title=Unexpected local dependency::Runtime requirements must resolve from PyPI."
cat "${runtime_requirements_file}"
exit 1
fi
- name: Build distributions
id: build
run: |
release_version="${{ steps.release.outputs.version }}"
dist_dir="build/release/dist"
rm -rf build/release
mkdir -p "${dist_dir}"
shopt -s nullglob
uv build --wheel --sdist --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
source_distributions=("${dist_dir}"/*.tar.gz)
if [[ "${#source_distributions[@]}" -ne 1 ]]; then
echo "::error title=Unexpected source distribution count::Expected one source distribution, found ${#source_distributions[@]}."
exit 1
fi
wheel_path="${project_wheels[0]}"
wheel_name="$(basename "${wheel_path}")"
source_distribution_path="${source_distributions[0]}"
source_distribution_name="$(basename "${source_distribution_path}")"
case "${wheel_name}" in
src_py_lib-"${release_version}"-*.whl)
;;
*)
echo "::error title=Wheel version mismatch::Expected wheel version ${release_version}, got '${wheel_name}'."
exit 1
;;
esac
case "${source_distribution_name}" in
src_py_lib-"${release_version}".tar.gz)
;;
*)
echo "::error title=Source distribution version mismatch::Expected source distribution version ${release_version}, got '${source_distribution_name}'."
exit 1
;;
esac
wheel_checksum_path="${wheel_path}.sha256"
source_distribution_checksum_path="${source_distribution_path}.sha256"
(
cd "$(dirname "${wheel_path}")"
shasum -a 256 "${wheel_name}" > "$(basename "${wheel_checksum_path}")"
shasum -a 256 "${source_distribution_name}" > "$(basename "${source_distribution_checksum_path}")"
)
{
echo "wheel_path=${wheel_path}"
echo "wheel_name=${wheel_name}"
echo "source_distribution_path=${source_distribution_path}"
echo "source_distribution_name=${source_distribution_name}"
echo "wheel_checksum_path=${wheel_checksum_path}"
echo "source_distribution_checksum_path=${source_distribution_checksum_path}"
} >> "${GITHUB_OUTPUT}"
- name: Smoke test installed wheel
env:
WHEEL_PATH: ${{ steps.build.outputs.wheel_path }}
run: |
validation_dir="$(mktemp -d)"
python -m venv "${validation_dir}/venv"
. "${validation_dir}/venv/bin/activate"
python -m pip install "${GITHUB_WORKSPACE}/${WHEEL_PATH}"
(
cd "${validation_dir}"
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 }}"
source_distribution_name="${{ steps.build.outputs.source_distribution_name }}"
notes_path="build/release/release-notes.md"
cat > "${notes_path}" <<EOF
## Install
Install from PyPI:
\`\`\`sh
pip install src-py-lib
\`\`\`
Install from the release wheel:
\`\`\`sh
pip install "https://github.com/sourcegraph/src-py-lib/releases/download/${release_tag}/${wheel_name}"
\`\`\`
Source distribution:
\`\`\`sh
curl -LO "https://github.com/sourcegraph/src-py-lib/releases/download/${release_tag}/${source_distribution_name}"
\`\`\`
Or install this tag with uv:
\`\`\`sh
uv add "git+https://github.com/sourcegraph/src-py-lib.git@${release_tag}"
\`\`\`
Verify downloaded assets with the matching \`.sha256\` files.
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.source_distribution_path }}
${{ steps.build.outputs.wheel_checksum_path }}
${{ steps.build.outputs.source_distribution_checksum_path }}
${{ steps.notes.outputs.path }}
- name: Upload PyPI artifact
uses: actions/upload-artifact@v7
with:
name: pypi-distributions
path: |
${{ steps.build.outputs.wheel_path }}
${{ steps.build.outputs.source_distribution_path }}
github-release:
name: Publish GitHub release assets
needs: [release_ref, validate, wheel]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Download release assets
uses: actions/download-artifact@v8
with:
name: src-py-lib-release
path: release-assets
- name: Publish GitHub release assets
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ needs.release_ref.outputs.tag }}
run: |
release_tag="${RELEASE_TAG}"
notes_path="$(find release-assets -name release-notes.md -print -quit)"
mapfile -t release_assets < <(find release-assets -type f ! -name release-notes.md | sort)
if [[ -z "${notes_path}" ]]; then
echo "::error title=Missing release notes::release-notes.md was not found in release artifact."
exit 1
fi
if [[ "${#release_assets[@]}" -eq 0 ]]; then
echo "::error title=Missing release assets::No release assets were downloaded."
exit 1
fi
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}" "${release_assets[@]}" --clobber
else
gh release create "${release_tag}" \
"${release_assets[@]}" \
--title "${release_tag}" \
--notes-file "${notes_path}" \
--verify-tag
fi
pypi:
name: Publish PyPI package
needs: [validate, wheel]
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write
environment:
name: pypi
url: https://pypi.org/project/src-py-lib/
steps:
- name: Download built distribution
uses: actions/download-artifact@v8
with:
name: pypi-distributions
path: dist
- name: Publish PyPI package
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
skip-existing: true