Skip to content

Commit 46845e4

Browse files
mivertowskiclaude
andcommitted
Add publish-python.sh script for PyPI publishing
Script for publishing ringkernel Python package to PyPI using maturin: - Supports --dry-run for testing builds - Supports --test-pypi for publishing to TestPyPI first - Supports --status to check if version is published - Supports --features for building with cuda/benchmark features - Verifies maturin installation - Checks if version already published before attempting - Waits and verifies publication after upload Usage: ./scripts/publish-python.sh --dry-run ./scripts/publish-python.sh --test-pypi <TOKEN> ./scripts/publish-python.sh <PYPI_TOKEN> Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5d12c79 commit 46845e4

1 file changed

Lines changed: 319 additions & 0 deletions

File tree

scripts/publish-python.sh

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
#!/bin/bash
2+
#
3+
# RingKernel Python Package Publishing Script
4+
#
5+
# This script publishes the ringkernel Python package to PyPI using maturin.
6+
#
7+
# Usage:
8+
# ./scripts/publish-python.sh <PYPI_TOKEN>
9+
# ./scripts/publish-python.sh --dry-run # Build without publishing
10+
# ./scripts/publish-python.sh --test-pypi <TOKEN> # Publish to TestPyPI first
11+
# ./scripts/publish-python.sh --status # Check if version is published
12+
#
13+
# Prerequisites:
14+
# - maturin installed: pip install maturin
15+
# - PyPI token from: https://pypi.org/manage/account/token/
16+
#
17+
# Features to build (optional, specify with --features):
18+
# - cuda: CUDA device support
19+
# - benchmark: Benchmark framework
20+
#
21+
22+
set -e
23+
24+
# Colors for output
25+
RED='\033[0;31m'
26+
GREEN='\033[0;32m'
27+
YELLOW='\033[1;33m'
28+
BLUE='\033[0;34m'
29+
CYAN='\033[0;36m'
30+
NC='\033[0m' # No Color
31+
32+
# Configuration
33+
DRY_RUN=false
34+
TEST_PYPI=false
35+
STATUS_ONLY=false
36+
FEATURES=""
37+
PACKAGE_NAME="ringkernel"
38+
CRATE_DIR="crates/ringkernel-python"
39+
40+
# Parse arguments
41+
TOKEN=""
42+
for arg in "$@"; do
43+
case $arg in
44+
--dry-run)
45+
DRY_RUN=true
46+
shift
47+
;;
48+
--test-pypi)
49+
TEST_PYPI=true
50+
shift
51+
;;
52+
--status)
53+
STATUS_ONLY=true
54+
shift
55+
;;
56+
--features=*)
57+
FEATURES="${arg#*=}"
58+
shift
59+
;;
60+
--help|-h)
61+
echo "RingKernel Python Package Publisher"
62+
echo ""
63+
echo "Usage: $0 [OPTIONS] [TOKEN]"
64+
echo ""
65+
echo "Options:"
66+
echo " --dry-run Build wheels without publishing"
67+
echo " --test-pypi Publish to TestPyPI instead of PyPI"
68+
echo " --status Check if current version is published"
69+
echo " --features=FEAT Comma-separated features (cuda,benchmark)"
70+
echo " --help Show this help message"
71+
echo ""
72+
echo "Examples:"
73+
echo " $0 --dry-run # Test build"
74+
echo " $0 --test-pypi <TOKEN> # Publish to TestPyPI"
75+
echo " $0 <TOKEN> # Publish to PyPI"
76+
echo " $0 --features=cuda,benchmark <TOKEN>"
77+
echo ""
78+
echo "Get your PyPI token from: https://pypi.org/manage/account/token/"
79+
echo "Get TestPyPI token from: https://test.pypi.org/manage/account/token/"
80+
exit 0
81+
;;
82+
*)
83+
if [ -z "$TOKEN" ] && [[ ! "$arg" =~ ^-- ]]; then
84+
TOKEN="$arg"
85+
fi
86+
;;
87+
esac
88+
done
89+
90+
print_header() {
91+
echo ""
92+
echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
93+
echo -e "${BLUE} $1${NC}"
94+
echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
95+
echo ""
96+
}
97+
98+
print_step() {
99+
echo -e "${GREEN}${NC} $1"
100+
}
101+
102+
print_warning() {
103+
echo -e "${YELLOW}${NC} $1"
104+
}
105+
106+
print_error() {
107+
echo -e "${RED}${NC} $1"
108+
}
109+
110+
print_success() {
111+
echo -e "${GREEN}${NC} $1"
112+
}
113+
114+
get_version() {
115+
# Get version from pyproject.toml
116+
grep '^version = ' "$CRATE_DIR/pyproject.toml" | sed 's/version = "\(.*\)"/\1/' | head -1
117+
}
118+
119+
check_pypi_published() {
120+
local version=$1
121+
local registry_url="https://pypi.org/pypi/$PACKAGE_NAME/$version/json"
122+
123+
if [ "$TEST_PYPI" = true ]; then
124+
registry_url="https://test.pypi.org/pypi/$PACKAGE_NAME/$version/json"
125+
fi
126+
127+
local response=$(curl -s -o /dev/null -w "%{http_code}" "$registry_url" 2>/dev/null)
128+
if [ "$response" = "200" ]; then
129+
return 0
130+
fi
131+
return 1
132+
}
133+
134+
check_maturin() {
135+
if ! command -v maturin &> /dev/null; then
136+
print_error "maturin is not installed!"
137+
echo ""
138+
echo "Install it with:"
139+
echo " pip install maturin"
140+
echo ""
141+
echo "Or with pipx:"
142+
echo " pipx install maturin"
143+
exit 1
144+
fi
145+
print_success "maturin $(maturin --version | cut -d' ' -f2) found"
146+
}
147+
148+
# Main script
149+
print_header "RingKernel Python Package Publisher"
150+
151+
# Change to workspace root
152+
cd "$(dirname "$0")/.."
153+
154+
# Check prerequisites
155+
print_step "Checking prerequisites..."
156+
check_maturin
157+
158+
# Verify crate directory exists
159+
if [ ! -d "$CRATE_DIR" ]; then
160+
print_error "Crate directory not found: $CRATE_DIR"
161+
exit 1
162+
fi
163+
print_success "Found $CRATE_DIR"
164+
165+
VERSION=$(get_version)
166+
if [ -z "$VERSION" ]; then
167+
print_error "Could not determine version from pyproject.toml"
168+
exit 1
169+
fi
170+
171+
# Determine registry
172+
REGISTRY="PyPI"
173+
REGISTRY_URL="https://pypi.org/project/$PACKAGE_NAME/"
174+
if [ "$TEST_PYPI" = true ]; then
175+
REGISTRY="TestPyPI"
176+
REGISTRY_URL="https://test.pypi.org/project/$PACKAGE_NAME/"
177+
fi
178+
179+
echo ""
180+
echo "Configuration:"
181+
echo " Package: $PACKAGE_NAME"
182+
echo " Version: $VERSION"
183+
echo " Registry: $REGISTRY"
184+
echo " Features: ${FEATURES:-none}"
185+
echo " Dry run: $DRY_RUN"
186+
echo " Crate dir: $CRATE_DIR"
187+
188+
# Status check
189+
if [ "$STATUS_ONLY" = true ]; then
190+
print_header "Publishing Status"
191+
echo -n " $PACKAGE_NAME@$VERSION on $REGISTRY: "
192+
if check_pypi_published "$VERSION"; then
193+
echo -e "${GREEN}✔ published${NC}"
194+
echo ""
195+
echo "View at: $REGISTRY_URL$VERSION/"
196+
else
197+
echo -e "${YELLOW}○ not published${NC}"
198+
fi
199+
exit 0
200+
fi
201+
202+
# Check if already published
203+
print_header "Checking Publishing Status"
204+
echo -n " $PACKAGE_NAME@$VERSION on $REGISTRY: "
205+
if check_pypi_published "$VERSION"; then
206+
echo -e "${GREEN}✔ already published${NC}"
207+
echo ""
208+
print_success "Version $VERSION is already published on $REGISTRY"
209+
echo ""
210+
echo "View at: $REGISTRY_URL$VERSION/"
211+
exit 0
212+
else
213+
echo -e "${YELLOW}○ not published${NC}"
214+
fi
215+
216+
# Validate token for real publish
217+
if [ "$DRY_RUN" = false ] && [ -z "$TOKEN" ]; then
218+
echo ""
219+
print_error "No PyPI token provided!"
220+
echo ""
221+
echo "Usage: $0 <PYPI_TOKEN> [--test-pypi]"
222+
echo ""
223+
echo "Get your token from:"
224+
echo " PyPI: https://pypi.org/manage/account/token/"
225+
echo " TestPyPI: https://test.pypi.org/manage/account/token/"
226+
exit 1
227+
fi
228+
229+
# Build the package
230+
print_header "Building Package"
231+
232+
cd "$CRATE_DIR"
233+
234+
BUILD_ARGS="--release"
235+
if [ -n "$FEATURES" ]; then
236+
BUILD_ARGS="$BUILD_ARGS --features $FEATURES"
237+
fi
238+
239+
print_step "Running: maturin build $BUILD_ARGS"
240+
echo ""
241+
242+
if maturin build $BUILD_ARGS; then
243+
print_success "Build completed successfully"
244+
else
245+
print_error "Build failed"
246+
exit 1
247+
fi
248+
249+
# Show built wheels
250+
echo ""
251+
print_step "Built wheels:"
252+
ls -la target/wheels/*.whl 2>/dev/null | sed 's/^/ /'
253+
254+
if [ "$DRY_RUN" = true ]; then
255+
print_header "Dry Run Complete"
256+
print_success "Package built successfully (not published)"
257+
echo ""
258+
echo "Wheels are in: $CRATE_DIR/target/wheels/"
259+
echo ""
260+
echo "To publish for real, run:"
261+
echo " $0 <PYPI_TOKEN>"
262+
exit 0
263+
fi
264+
265+
# Publish
266+
print_header "Publishing to $REGISTRY"
267+
268+
PUBLISH_ARGS="--username __token__ --password $TOKEN"
269+
if [ "$TEST_PYPI" = true ]; then
270+
PUBLISH_ARGS="$PUBLISH_ARGS --repository-url https://test.pypi.org/legacy/"
271+
fi
272+
if [ -n "$FEATURES" ]; then
273+
PUBLISH_ARGS="$PUBLISH_ARGS --features $FEATURES"
274+
fi
275+
276+
print_step "Publishing $PACKAGE_NAME@$VERSION to $REGISTRY..."
277+
echo ""
278+
279+
# Hide token in output
280+
if maturin publish $PUBLISH_ARGS 2>&1 | grep -v "$TOKEN"; then
281+
echo ""
282+
print_success "Successfully published $PACKAGE_NAME@$VERSION to $REGISTRY!"
283+
else
284+
echo ""
285+
print_error "Failed to publish"
286+
exit 1
287+
fi
288+
289+
# Verify publication
290+
print_header "Verifying Publication"
291+
echo "Waiting for package to appear on $REGISTRY..."
292+
sleep 5
293+
294+
MAX_WAIT=60
295+
WAITED=0
296+
while [ $WAITED -lt $MAX_WAIT ]; do
297+
if check_pypi_published "$VERSION"; then
298+
print_success "Package is now available on $REGISTRY"
299+
break
300+
fi
301+
echo -ne "\r Waiting... [$WAITED/$MAX_WAIT seconds]"
302+
sleep 5
303+
WAITED=$((WAITED + 5))
304+
done
305+
306+
echo ""
307+
308+
# Summary
309+
print_header "Summary"
310+
print_success "Published: $PACKAGE_NAME@$VERSION"
311+
echo ""
312+
echo "Install with:"
313+
if [ "$TEST_PYPI" = true ]; then
314+
echo " pip install --index-url https://test.pypi.org/simple/ $PACKAGE_NAME"
315+
else
316+
echo " pip install $PACKAGE_NAME"
317+
fi
318+
echo ""
319+
echo "View at: $REGISTRY_URL"

0 commit comments

Comments
 (0)