name: Build Pip package on: workflow_call: inputs: full_tests: description: Run the full package compatibility matrix instead of one release smoke check required: false default: true type: boolean env: UV_VERSION: "0.11.3" jobs: build: name: build distributions runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Set up Python 3.13 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.13" architecture: x64 - name: Install uv uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 with: version: ${{ env.UV_VERSION }} enable-cache: false cache-dependency-glob: uv.lock - name: Build every distribution run: | set -Eeuo pipefail uv build --no-cache --all-packages --no-sources --out-dir dist --clear shopt -s nullglob artifacts=(dist/*) wheels=(dist/archivebox-*.whl) sdists=(dist/archivebox-*.tar.gz) [[ "${#artifacts[@]}" -gt 0 ]] [[ "${#wheels[@]}" -eq 1 ]] [[ "${#sdists[@]}" -eq 1 ]] printf '%s\n' "$GITHUB_SHA" > dist/COMMIT_SHA uv run --no-cache --no-project python - <<'PY' import hashlib from pathlib import Path root = Path("dist") wheels = list(root.glob("archivebox-*.whl")) sdists = list(root.glob("archivebox-*.tar.gz")) if len(wheels) != 1 or len(sdists) != 1: raise SystemExit("Expected exactly one ArchiveBox wheel and one sdist") artifacts = [root / "COMMIT_SHA", wheels[0], sdists[0]] checksum_lines = [] for artifact in artifacts: digest = hashlib.sha256(artifact.read_bytes()).hexdigest() line = f"{digest} {artifact.name}" checksum_lines.append(line) print(line) Path("dist/SHA256SUMS").write_text("\n".join(checksum_lines) + "\n") PY - name: Release wheel import and CLI smoke if: ${{ !inputs.full_tests }} shell: bash run: | set -Eeuo pipefail shopt -s nullglob wheels=(dist/archivebox-*.whl) [[ "${#wheels[@]}" -eq 1 ]] uv sync --locked --no-cache --no-sources --no-dev --no-install-project uv pip install --no-cache --no-deps --python .venv/bin/python "${wheels[0]}" unset PYTHONPATH cd "$(mktemp -d)" VIRTUAL_ENV="$GITHUB_WORKSPACE/.venv" uv run --no-cache --active --no-project python -c 'import archivebox' VIRTUAL_ENV="$GITHUB_WORKSPACE/.venv" uv run --no-cache --active --no-project archivebox version - name: Upload every distribution uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: python-distributions path: dist/* if-no-files-found: error retention-days: 7 install-smoke: name: install ${{ matrix.artifact }} / Python ${{ matrix.python }} / ${{ matrix.os }} if: inputs.full_tests needs: build runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-24.04, macos-15] python: ["3.13", "3.14"] artifact: [wheel, sdist] steps: - name: Checkout repository uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 1 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: ${{ matrix.python }} - name: Install uv uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 with: version: ${{ env.UV_VERSION }} enable-cache: false cache-dependency-glob: uv.lock - name: Download distributions uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: python-distributions path: ${{ runner.temp }}/python-distributions - name: Install and smoke-test the built ${{ matrix.artifact }} shell: bash run: | set -Eeuo pipefail shopt -s nullglob case "${{ matrix.artifact }}" in wheel) candidates=("$RUNNER_TEMP"/python-distributions/archivebox-*.whl) ;; sdist) candidates=("$RUNNER_TEMP"/python-distributions/archivebox-*.tar.gz) ;; *) exit 2 ;; esac [[ "${#candidates[@]}" -eq 1 ]] artifact="${candidates[0]}" smoke_root="$(mktemp -d "$RUNNER_TEMP/archivebox-package-smoke.XXXXXX")" trap 'rm -rf "$smoke_root"' EXIT smoke_env="$smoke_root/env" data_dir="$smoke_root/data" mkdir -p "$data_dir" dependency_artifacts=() while IFS= read -r dependency_artifact; do dependency_artifacts+=("$dependency_artifact") done < <(uv run --no-cache --no-project python - <<'PY' import tomllib locked = { package["name"]: package for package in tomllib.load(open("uv.lock", "rb"))["package"] } for package_name in ("abx-dl", "abx-plugins", "abxpkg", "abxbus"): package = locked[package_name] wheels = package.get("wheels") or [] if len(wheels) != 1: raise SystemExit(f"Expected exactly one locked wheel for {package_name}") print(wheels[0]["url"]) PY ) uv venv --python "${{ matrix.python }}" "$smoke_env" uv pip install --no-cache \ --python "$smoke_env" \ "${dependency_artifacts[@]}" \ "$artifact" unset PYTHONPATH cd "$data_dir" VIRTUAL_ENV="$smoke_env" uv run --no-cache --active --no-project --no-sync python - <<'PY' import os from pathlib import Path import archivebox installed_path = Path(archivebox.__file__).resolve() environment_path = Path(os.environ["VIRTUAL_ENV"]).resolve() workspace_path = Path(os.environ["GITHUB_WORKSPACE"]).resolve() assert installed_path.is_relative_to(environment_path), (installed_path, environment_path) assert not installed_path.is_relative_to(workspace_path), (installed_path, workspace_path) print(installed_path) PY VIRTUAL_ENV="$smoke_env" uv run --no-cache --active --no-project --no-sync archivebox version VIRTUAL_ENV="$smoke_env" uv run --no-cache --active --no-project --no-sync archivebox init VIRTUAL_ENV="$smoke_env" uv run --no-cache --active --no-project --no-sync archivebox status if [[ "${{ runner.os }}" == "Linux" ]]; then root_tool_dir="/root/archivebox-package-smoke-${{ matrix.artifact }}-${{ matrix.python }}" uv_binary="$(command -v uv)" sudo rm -rf "$root_tool_dir" sudo mkdir -p "$root_tool_dir/data" root_install_args=() for dependency_artifact in "${dependency_artifacts[@]}"; do root_install_args+=(--with "$dependency_artifact") done sudo UV_TOOL_DIR="$root_tool_dir/tools" UV_TOOL_BIN_DIR="$root_tool_dir/bin" \ "$uv_binary" tool install --no-cache --python "${{ matrix.python }}" \ "${root_install_args[@]}" "$artifact" sudo useradd --system --create-home --home-dir /var/lib/archivebox --shell /bin/bash archivebox 2>/dev/null || true sudo rm -rf /var/lib/archivebox sudo env PATH="$root_tool_dir/bin:$PATH" \ bash -c "cd '$root_tool_dir/data' && archivebox init" sudo stat -c %U /var/lib/archivebox | grep -qx archivebox sudo stat -c %U "$root_tool_dir/data" | grep -qx archivebox if sudo find "$root_tool_dir/data" -xdev -user root -print -quit | grep -q .; then echo "Root-owned files remain in $root_tool_dir/data" >&2 exit 1 fi sudo mkdir "$root_tool_dir/external" sudo ln -s "$root_tool_dir/external" "$root_tool_dir/data/external-link" sudo env PATH="$root_tool_dir/bin:$PATH" \ bash -c "cd '$root_tool_dir/data' && archivebox status" sudo stat -c %U "$root_tool_dir/external" | grep -qx root sudo mkdir "$root_tool_dir/data-universal-init" sudo env PATH="$root_tool_dir/bin:$PATH" \ bash -c "cd '$root_tool_dir/data-universal-init' && archivebox status --init" sudo stat -c %U "$root_tool_dir/data-universal-init" | grep -qx archivebox sudo mkdir "$root_tool_dir/data-install" sudo env PATH="$root_tool_dir/bin:$PATH" \ bash -c "cd '$root_tool_dir/data-install' && archivebox install --dry-run" sudo stat -c %U "$root_tool_dir/data-install" | grep -qx archivebox sudo useradd --create-home archivebox-smoke-user smoke_user_group="$(id -gn archivebox-smoke-user)" sudo mkdir "$root_tool_dir/data-mixed-owner" sudo chown archivebox-smoke-user:root "$root_tool_dir/data-mixed-owner" sudo env PATH="$root_tool_dir/bin:$PATH" \ bash -c "cd '$root_tool_dir/data-mixed-owner' && archivebox init" sudo stat -c %U:%G "$root_tool_dir/data-mixed-owner" | grep -qx "archivebox-smoke-user:$smoke_user_group" if sudo find "$root_tool_dir/data-mixed-owner" -xdev -user root -print -quit | grep -q .; then echo "Root-owned files remain in $root_tool_dir/data-mixed-owner" >&2 exit 1 fi sudo rm -rf "$root_tool_dir" fi