Skip to content

Commit 5159847

Browse files
FindHaometa-codesync[bot]
authored andcommitted
On-Demand Nightly PyPI Publishing (#216)
Summary: This PR implements on-demand nightly publishing for the tritonparse package. Instead of publishing a new nightly version every day regardless of changes, the workflow now checks if there are new commits since the last nightly release and skips publishing if there are none. ## Changes ### New File: `.github/scripts/check_new_commits.sh` A standalone script that: 1. Queries PyPI API to get the latest nightly version (e.g., `0.3.2.dev20251208071617`) 2. Extracts the timestamp from the version string 3. Uses `git log --since` to check for new commits after that timestamp 4. Outputs `should_publish=true/false` to `GITHUB_OUTPUT` Key features: - **Network retry mechanism**: 3 retries with 5s delay between attempts - **Fail-open design**: If PyPI API is unreachable, proceeds with publish (never blocks releases due to network issues) - **Edge case handling**: Handles first nightly release, unparseable versions, etc. ### Modified: `.github/workflows/nightly-pypi.yml` - Added a new step to run `check_new_commits.sh` before computing the nightly version - All subsequent steps (build, check, publish) are conditioned on `should_publish != 'false'` - Tag-based releases bypass the check entirely ## Workflow Logic ``` Trigger (schedule/workflow_dispatch/tag) │ ▼ Is tag push? ───Yes──► Build and Publish directly │ No ▼ Run check_new_commits.sh │ ▼ Has new commits? ───No──► Skip (exit early) │ Yes ▼ Compute version → Build → Publish ``` ## Benefits - **Reduces noise**: No more daily releases with identical content - **Saves resources**: Skips unnecessary CI builds - **Cleaner PyPI history**: Only releases with actual changes - **Robust**: Network failures don't block legitimate releases ## Testing Run locally with: ```bash cd tritonparse_oss GITHUB_OUTPUT=/tmp/gh_output.txt bash .github/scripts/check_new_commits.sh cat /tmp/gh_output.txt ``` Pull Request resolved: #216 Reviewed By: xuzhao9 Differential Revision: D88696841 Pulled By: FindHao fbshipit-source-id: 04e2ff74fc7cfa1fda2769e5a0fdaa975988c964
1 parent c209bb9 commit 5159847

File tree

2 files changed

+125
-2
lines changed

2 files changed

+125
-2
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/bin/bash
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
# Check if there are new commits since the last nightly PyPI release.
4+
# This script is used by nightly-pypi.yml to implement on-demand nightly publishing.
5+
#
6+
# Exit code:
7+
# Always exits with 0 (success). The publish decision is communicated via GITHUB_OUTPUT.
8+
#
9+
# Output:
10+
# Sets GITHUB_OUTPUT variable: should_publish=true/false
11+
# - true: New commits found or check skipped due to errors (fail-open behavior)
12+
# - false: No new commits since last nightly release
13+
14+
set -o pipefail
15+
# Note: set -e is intentionally not used to allow explicit error handling.
16+
# The script implements fail-open behavior where errors should not block publishing.
17+
18+
# Configuration
19+
MAX_RETRIES=3
20+
RETRY_DELAY=5
21+
CURL_TIMEOUT=10
22+
PACKAGE_NAME="${PACKAGE_NAME:-tritonparse}"
23+
24+
# Dependencies: This script requires 'jq' and 'curl' to be installed.
25+
# These are pre-installed on ubuntu-latest GitHub Actions runners.
26+
27+
# Function: Fetch latest nightly version from PyPI with retry
28+
fetch_latest_nightly() {
29+
local retries=0
30+
31+
while [ $retries -lt $MAX_RETRIES ]; do
32+
local response
33+
response=$(curl -s --max-time $CURL_TIMEOUT \
34+
"https://pypi.org/pypi/${PACKAGE_NAME}/json" 2>/dev/null)
35+
local curl_exit=$?
36+
37+
if [ $curl_exit -eq 0 ] && [ -n "$response" ]; then
38+
# Try to parse and extract latest dev version
39+
local latest
40+
latest=$(echo "$response" | \
41+
jq -r '.releases | keys[] | select(contains(".dev"))' 2>/dev/null | \
42+
sort -V | tail -1)
43+
local jq_exit=$?
44+
45+
if [ $jq_exit -eq 0 ] && [ -n "$latest" ]; then
46+
echo "$latest"
47+
return 0
48+
fi
49+
fi
50+
51+
retries=$((retries + 1))
52+
echo "::warning::PyPI API request failed (attempt $retries/$MAX_RETRIES)"
53+
54+
if [ $retries -lt $MAX_RETRIES ]; then
55+
echo "Retrying in ${RETRY_DELAY}s..."
56+
sleep $RETRY_DELAY
57+
fi
58+
done
59+
60+
echo "::warning::All $MAX_RETRIES attempts failed"
61+
return 1
62+
}
63+
64+
main() {
65+
echo "Checking for new commits since last nightly release..."
66+
67+
# Step 1: Fetch latest nightly version (with retry)
68+
LATEST_NIGHTLY=$(fetch_latest_nightly)
69+
FETCH_STATUS=$?
70+
71+
# Step 2: Network request failed -> skip check, proceed with publish
72+
if [ $FETCH_STATUS -ne 0 ]; then
73+
echo "::warning::Failed to fetch PyPI data after $MAX_RETRIES attempts"
74+
echo "::warning::Skipping commit check, proceeding with publish"
75+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
76+
exit 0
77+
fi
78+
79+
# Step 3: No nightly version exists -> first nightly release
80+
if [ -z "$LATEST_NIGHTLY" ]; then
81+
echo "No existing nightly version found, proceeding with publish"
82+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
83+
exit 0
84+
fi
85+
86+
echo "Latest nightly on PyPI: $LATEST_NIGHTLY"
87+
88+
# Step 4: Extract timestamp from version (format: X.Y.Z.devYYYYMMDDHHMMSS)
89+
TIMESTAMP=$(echo "$LATEST_NIGHTLY" | sed -n 's/.*\.dev\([0-9]\{14\}\).*/\1/p')
90+
91+
if [ -z "$TIMESTAMP" ]; then
92+
echo "::warning::Cannot parse timestamp from version, proceeding with publish"
93+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
94+
exit 0
95+
fi
96+
97+
# Step 5: Convert to date format for git log
98+
SINCE_DATE="${TIMESTAMP:0:4}-${TIMESTAMP:4:2}-${TIMESTAMP:6:2} ${TIMESTAMP:8:2}:${TIMESTAMP:10:2}:${TIMESTAMP:12:2} UTC"
99+
echo "Last nightly published at: $SINCE_DATE"
100+
101+
# Step 6: Check for new commits since last nightly
102+
COMMITS_SINCE=$(git log --since="$SINCE_DATE" --oneline | wc -l)
103+
104+
if [ "$COMMITS_SINCE" -eq 0 ]; then
105+
echo "No new commits since last nightly, skipping publish"
106+
echo "should_publish=false" >> "$GITHUB_OUTPUT"
107+
exit 0
108+
else
109+
echo "Found $COMMITS_SINCE new commit(s) since last nightly"
110+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
111+
exit 0
112+
fi
113+
}
114+
115+
main

.github/workflows/nightly-pypi.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@ jobs:
2323
- uses: actions/setup-python@v5
2424
with:
2525
python-version: "3.11"
26+
27+
- name: Check for new commits since last nightly
28+
id: check
29+
if: github.ref_type != 'tag'
30+
run: bash .github/scripts/check_new_commits.sh
31+
2632
- name: Compute nightly version from latest tag (next patch + timestamp)
2733
id: ver
28-
if: github.ref_type != 'tag'
34+
if: github.ref_type != 'tag' && steps.check.outputs.should_publish != 'false'
2935
run: |
3036
# Get latest tag; allow 'v' prefix; fail if none
3137
if ! TAG=$(git describe --tags --abbrev=0 2>/dev/null); then
@@ -46,6 +52,7 @@ jobs:
4652
echo "Computed nightly version: ${NEXT}.dev${DATE}"
4753
4854
- name: Build sdist/wheel
55+
if: github.ref_type == 'tag' || steps.check.outputs.should_publish != 'false'
4956
run: |
5057
python -m pip install --upgrade pip
5158
pip install build setuptools-scm
@@ -55,12 +62,13 @@ jobs:
5562
python -m build
5663
5764
- name: Check metadata
65+
if: github.ref_type == 'tag' || steps.check.outputs.should_publish != 'false'
5866
run: |
5967
pip install twine
6068
twine check dist/*
6169
6270
- name: Publish to PyPI (API token)
63-
if: github.event_name == 'schedule' || github.ref_type == 'tag'
71+
if: (github.event_name == 'schedule' || github.ref_type == 'tag') && steps.check.outputs.should_publish != 'false'
6472
uses: pypa/gh-action-pypi-publish@release/v1
6573
with:
6674
user: __token__

0 commit comments

Comments
 (0)