feat(tinytorch): single source of truth for version management

- install.sh now fetches version from GitHub tags API instead of hardcoding
- README.md badge uses dynamic shields.io GitHub tag filter
- Add release.sh script for version bumping and tagging workflow

Version is now managed solely in pyproject.toml. Other files read it at
runtime or via GitHub API, eliminating version drift across files.
This commit is contained in:
Vijay Janapa Reddi
2026-01-23 14:48:55 -05:00
parent 840eae5178
commit eea7f690dc
3 changed files with 270 additions and 2 deletions

View File

@@ -4,7 +4,7 @@
### Build Your Own ML Framework From Scratch
[![Version](https://img.shields.io/badge/version-0.1.4-D4740C?logo=fireship&logoColor=white)](https://github.com/harvard-edge/cs249r_book/releases?q=tinytorch)
[![Version](https://img.shields.io/github/v/tag/harvard-edge/cs249r_book?filter=tinytorch-v*&label=version&color=D4740C&logo=fireship&logoColor=white)](https://github.com/harvard-edge/cs249r_book/releases?q=tinytorch)
[![Status](https://img.shields.io/badge/status-preview-orange?logo=github)](https://github.com/harvard-edge/cs249r_book/discussions/1076)
[![Docs](https://img.shields.io/badge/docs-mlsysbook.ai-blue?logo=readthedocs)](https://mlsysbook.ai/tinytorch)
[![Python](https://img.shields.io/badge/python-3.8+-3776ab?logo=python&logoColor=white)](https://python.org)

218
tinytorch/scripts/release.sh Executable file
View File

@@ -0,0 +1,218 @@
#!/bin/bash
# ============================================================================
# TinyTorch Release Script
# ============================================================================
#
# USAGE
# -----
# ./scripts/release.sh 0.1.5
# ./scripts/release.sh 0.1.5 --dry-run
#
# WHAT THIS SCRIPT DOES
# ---------------------
# 1. Updates version in pyproject.toml (single source of truth)
# 2. Updates version in settings.ini (for nbdev compatibility)
# 3. Creates a git commit with the version bump
# 4. Creates a git tag: tinytorch-v{VERSION}
# 5. Pushes commit and tag to origin
#
# SINGLE SOURCE OF TRUTH
# ----------------------
# pyproject.toml is THE source of truth for version.
# Other files read from it at runtime:
# - tinytorch/__init__.py → reads pyproject.toml
# - tito/main.py → reads pyproject.toml
# - install.sh → fetches from GitHub tags API
# - README.md badge → dynamic shields.io badge
#
# settings.ini is updated for nbdev compatibility but is not authoritative.
#
# ============================================================================
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
print_step() { echo -e "${BLUE}${NC} $1"; }
print_success() { echo -e "${GREEN}${NC} $1"; }
print_error() { echo -e "${RED}${NC} $1"; }
print_warning() { echo -e "${YELLOW}!${NC} $1"; }
# ============================================================================
# Parse Arguments
# ============================================================================
VERSION=""
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN=true
shift
;;
-*)
print_error "Unknown option: $1"
echo "Usage: $0 VERSION [--dry-run]"
exit 1
;;
*)
if [ -z "$VERSION" ]; then
VERSION="$1"
else
print_error "Unexpected argument: $1"
exit 1
fi
shift
;;
esac
done
if [ -z "$VERSION" ]; then
print_error "Version required"
echo ""
echo "Usage: $0 VERSION [--dry-run]"
echo ""
echo "Examples:"
echo " $0 0.1.5"
echo " $0 0.2.0 --dry-run"
exit 1
fi
# Validate version format (semver-ish)
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
print_error "Invalid version format: $VERSION"
echo "Expected format: MAJOR.MINOR.PATCH (e.g., 0.1.5)"
exit 1
fi
# ============================================================================
# Verify we're in the right directory
# ============================================================================
if [ ! -f "pyproject.toml" ]; then
print_error "pyproject.toml not found"
echo "Run this script from the tinytorch/ directory"
exit 1
fi
# Get current version
CURRENT_VERSION=$(grep -E "^version" pyproject.toml | head -1 | sed 's/.*= *"\([^"]*\)".*/\1/')
if [ -z "$CURRENT_VERSION" ]; then
print_error "Could not read current version from pyproject.toml"
exit 1
fi
TAG_NAME="tinytorch-v${VERSION}"
# ============================================================================
# Show plan
# ============================================================================
echo ""
echo -e "${BOLD}TinyTorch Release${NC}"
echo ""
echo " Current version: ${CURRENT_VERSION}"
echo " New version: ${VERSION}"
echo " Tag: ${TAG_NAME}"
echo ""
if [ "$DRY_RUN" = true ]; then
print_warning "DRY RUN - No changes will be made"
echo ""
fi
# ============================================================================
# Check for uncommitted changes
# ============================================================================
if ! git diff --quiet || ! git diff --cached --quiet; then
print_warning "You have uncommitted changes"
git status --short
echo ""
if [ "$DRY_RUN" = false ]; then
read -p "Continue anyway? [y/N] " -r
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Aborted"
exit 1
fi
fi
fi
# ============================================================================
# Check if tag already exists
# ============================================================================
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
print_error "Tag ${TAG_NAME} already exists"
exit 1
fi
# ============================================================================
# Update versions
# ============================================================================
print_step "Updating pyproject.toml..."
if [ "$DRY_RUN" = false ]; then
sed -i.bak "s/^version = \".*\"/version = \"${VERSION}\"/" pyproject.toml
rm -f pyproject.toml.bak
print_success "Updated pyproject.toml"
else
echo " Would update: version = \"${VERSION}\""
fi
print_step "Updating settings.ini..."
if [ "$DRY_RUN" = false ]; then
sed -i.bak "s/^version = .*/version = ${VERSION}/" settings.ini
rm -f settings.ini.bak
print_success "Updated settings.ini"
else
echo " Would update: version = ${VERSION}"
fi
# ============================================================================
# Git commit and tag
# ============================================================================
print_step "Creating git commit..."
if [ "$DRY_RUN" = false ]; then
git add pyproject.toml settings.ini
git commit -m "release: tinytorch v${VERSION}"
print_success "Created commit"
else
echo " Would commit: release: tinytorch v${VERSION}"
fi
print_step "Creating git tag ${TAG_NAME}..."
if [ "$DRY_RUN" = false ]; then
git tag -a "$TAG_NAME" -m "TinyTorch v${VERSION}"
print_success "Created tag ${TAG_NAME}"
else
echo " Would create tag: ${TAG_NAME}"
fi
# ============================================================================
# Push
# ============================================================================
print_step "Pushing to origin..."
if [ "$DRY_RUN" = false ]; then
git push origin HEAD
git push origin "$TAG_NAME"
print_success "Pushed commit and tag"
else
echo " Would push: HEAD and ${TAG_NAME}"
fi
# ============================================================================
# Done
# ============================================================================
echo ""
if [ "$DRY_RUN" = false ]; then
print_success "Release v${VERSION} complete!"
echo ""
echo "Next steps:"
echo " 1. Create GitHub release: https://github.com/harvard-edge/cs249r_book/releases/new?tag=${TAG_NAME}"
echo " 2. Students can update: tito system update"
else
print_warning "Dry run complete - no changes made"
fi
echo ""

View File

@@ -67,10 +67,14 @@ set -e # Exit on any error
# TINYTORCH_VERSION=0.1.5 TINYTORCH_BRANCH=feature/foo ./install.sh
REPO_URL="https://github.com/harvard-edge/cs249r_book.git"
REPO_SHORT="harvard-edge/cs249r_book"
TAGS_API="https://api.github.com/repos/harvard-edge/cs249r_book/tags"
TAG_PREFIX="tinytorch-v"
BRANCH="${TINYTORCH_BRANCH:-main}"
INSTALL_DIR="${TINYTORCH_INSTALL_DIR:-tinytorch}"
SPARSE_PATH="tinytorch"
TINYTORCH_VERSION="${TINYTORCH_VERSION:-0.1.4}"
# Version is fetched from GitHub tags (single source of truth)
# Can be overridden for testing: TINYTORCH_VERSION=0.1.5 ./install.sh
TINYTORCH_VERSION="${TINYTORCH_VERSION:-}"
# ============================================================================
# ANSI Color Codes (for terminal output)
@@ -132,6 +136,49 @@ command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Fetch latest version from GitHub tags API (single source of truth)
# Sets TINYTORCH_VERSION global variable
fetch_latest_version() {
# Skip if already set via environment variable
if [ -n "$TINYTORCH_VERSION" ]; then
return 0
fi
# Try curl first (more reliable across platforms)
if command_exists curl; then
local response
response=$(curl -fsSL --max-time 10 "$TAGS_API" 2>/dev/null) || true
if [ -n "$response" ]; then
# Parse JSON to find first tinytorch-v* tag
# Uses grep/sed for portability (no jq dependency)
local tag_name
tag_name=$(echo "$response" | grep -o "\"name\": *\"${TAG_PREFIX}[^\"]*\"" | head -1 | sed 's/.*"name": *"\([^"]*\)".*/\1/')
if [ -n "$tag_name" ]; then
TINYTORCH_VERSION="${tag_name#$TAG_PREFIX}"
return 0
fi
fi
fi
# Fallback: fetch pyproject.toml directly from raw.githubusercontent.com
if command_exists curl; then
local pyproject_url="https://raw.githubusercontent.com/${REPO_SHORT}/${BRANCH}/tinytorch/pyproject.toml"
local pyproject
pyproject=$(curl -fsSL --max-time 10 "$pyproject_url" 2>/dev/null) || true
if [ -n "$pyproject" ]; then
local version
version=$(echo "$pyproject" | grep -E "^version" | head -1 | sed 's/.*= *"\([^"]*\)".*/\1/')
if [ -n "$version" ]; then
TINYTORCH_VERSION="$version"
return 0
fi
fi
fi
# Final fallback: unknown version (will still work, just won't show version)
TINYTORCH_VERSION="latest"
}
# Check if Python version is 3.8+
check_python_version() {
local python_cmd="$1"
@@ -467,6 +514,9 @@ print_success_message() {
# ============================================================================
main() {
# Fetch version from GitHub (single source of truth: pyproject.toml via tags)
fetch_latest_version
print_banner
# Pre-flight checks