Files
cs249r_book/tinytorch/site/extra/install.sh
Vijay Janapa Reddi 173f28f88d fix(tinytorch): clean up Windows install fix comments from PR #1169
Polish the contributor's Windows fix with proper comments explaining
the Microsoft Store alias issue and WinError 32 file lock. Move
is_windows check closer to usage site for clarity.
2026-02-13 11:39:28 -05:00

613 lines
21 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# TinyTorch Installer
# ============================================================================
#
# USAGE
# -----
# curl -sSL mlsysbook.ai/tinytorch/install.sh | bash
#
# WHAT THIS SCRIPT DOES
# ---------------------
# 1. Checks prerequisites (git, Python 3.10+, venv module)
# 2. Asks where to install (default: ./tinytorch)
# 3. Shows installation plan and asks for confirmation
# 4. Downloads TinyTorch via git sparse checkout (minimal download)
# 5. Creates an isolated Python virtual environment (.venv/)
# 6. Installs all dependencies and the tito CLI
#
# AFTER INSTALLATION
# ------------------
# cd tinytorch
# source .venv/bin/activate
# tito setup # First-time profile setup
#
# WHAT GETS CREATED
# -----------------
# tinytorch/ # Created in your current directory
# ├── .venv/ # Python virtual environment
# ├── src/ # Module source notebooks (20 modules)
# ├── tinytorch/ # Package - your implementations go here
# ├── tito/ # CLI tool source
# ├── milestones/ # Historical ML recreations
# ├── tests/ # Test suites for your code
# ├── datasets/ # Sample datasets (tinydigits, tinytalks)
# ├── modules/ # Working directory (populated by tito)
# ├── bin/ # CLI entry point
# ├── requirements.txt # Python dependencies
# ├── pyproject.toml # Package configuration
# ├── settings.ini # nbdev configuration
# └── README.md, LICENSE # Documentation
#
# REQUIREMENTS
# ------------
# - git (any recent version)
# - Python 3.10 or higher
# - Python venv module (usually included; on Debian/Ubuntu: apt install python3-venv)
# - Internet connection to GitHub
#
# DOCUMENTATION
# -------------
# https://tinytorch.ai
# https://mlsysbook.ai/tinytorch/
#
# SOURCE
# ------
# https://github.com/harvard-edge/cs249r_book (tinytorch/ subdirectory)
#
# ============================================================================
set -e # Exit on any error
# ============================================================================
# Configuration
# ============================================================================
# These can be overridden via environment variables for testing:
# TINYTORCH_BRANCH=dev curl -sSL mlsysbook.ai/tinytorch/install.sh | bash
# TINYTORCH_VERSION=0.1.5 TINYTORCH_BRANCH=feature/foo ./install.sh
# TINYTORCH_NON_INTERACTIVE=1 ./install.sh # Skip all prompts (for CI)
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"
# Non-interactive mode: skip prompts, use defaults (for CI/testing)
NON_INTERACTIVE="${TINYTORCH_NON_INTERACTIVE:-}"
# 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)
# ============================================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m' # No Color / Reset
# ============================================================================
# Cleanup Handler
# Removes temporary files if script exits unexpectedly
# ============================================================================
TEMP_DIR=""
cleanup() {
if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT
trap 'echo ""; echo -e "${RED}Installation cancelled.${NC}"; exit 1' INT TERM
# ============================================================================
# Output Helpers
# ============================================================================
print_success() { echo -e "${GREEN}${NC} $1"; }
print_error() { echo -e "${RED}${NC} $1"; }
print_warning() { echo -e "${YELLOW}!${NC} $1"; }
print_info() { echo -e "${BLUE}${NC} $1"; }
# Spinner animation for long-running background tasks
spin() {
local pid=$1
local msg=$2
local spinchars='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r ${DIM}%s${NC} %s" "${spinchars:i++%10:1}" "$msg"
sleep 0.1
done
printf "\r %-50s\r" ""
}
print_banner() {
echo ""
echo -e " ${BOLD}Tiny${NC}${YELLOW}🔥Torch${NC} ${DIM}v${TINYTORCH_VERSION}${NC}"
echo -e " ${DIM}Don't import it. Build it.${NC}"
echo ""
}
# ============================================================================
# Utility Functions
# ============================================================================
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"
local version major minor
version=$($python_cmd -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null)
major=$($python_cmd -c "import sys; print(sys.version_info.major)" 2>/dev/null)
minor=$($python_cmd -c "import sys; print(sys.version_info.minor)" 2>/dev/null)
# Check for Python 3.8+ (Required for TinyTorch)
if [ "$major" -eq 3 ] && [ "$minor" -ge 8 ]; then
echo "$version"
return 0
elif [ "$major" -gt 3 ]; then
echo "$version"
return 0
else
echo "$version"
return 1
fi
}
# Find the best Python command (prioritize newer versions)
get_python_cmd() {
local platform
platform=$(get_platform)
# Check specific versions first, prioritizing newer versions
# On Windows, prefer 'python' to avoid Microsoft Store alias that
# resolves 'python3' to a stub and creates Unix-style venv paths.
# Contributed by @adil-mubashir-ch (PR #1169)
if [ "$platform" = "windows" ]; then
local candidates=("python")
else
local candidates=("python3.13" "python3.12" "python3.11" "python3.10" "python3.9" "python3.8" "python3" "python")
fi
for cmd in "${candidates[@]}"; do
if command_exists "$cmd"; then
# Verify this specific candidate actually meets the version requirement
if check_python_version "$cmd" >/dev/null 2>&1; then
echo "$cmd"
return 0
fi
fi
done
echo ""
}
# Find the system platform (linux, macos, windows)
# Contributed by @rnjema (PR #1105)
get_platform() {
local uname_out
uname_out=$(uname -s)
case "${uname_out}" in
Linux*) echo "linux";;
Darwin*) echo "macos";;
CYGWIN*|MINGW*|MSYS*) echo "windows";;
*) echo "unknown";;
esac
}
# ============================================================================
# Pre-flight Checks
# These run before any installation to catch problems early
# ============================================================================
check_write_permission() {
if ! touch ".tinytorch_write_test" 2>/dev/null; then
print_error "Cannot write to this directory"
echo " Check your permissions or cd to a writable directory."
exit 1
fi
rm -f ".tinytorch_write_test"
}
check_not_in_venv() {
if [ -n "$VIRTUAL_ENV" ]; then
print_warning "You're inside a virtual environment: $VIRTUAL_ENV"
echo " Consider deactivating first: deactivate"
echo ""
fi
}
check_internet() {
if ! git ls-remote --exit-code "$REPO_URL" >/dev/null 2>&1; then
print_error "Cannot reach GitHub"
echo " Check your internet connection and try again."
exit 1
fi
print_success "GitHub reachable"
}
check_prerequisites() {
local errors=0
# Check for git
if command_exists git; then
GIT_VERSION=$(git --version | cut -d' ' -f3)
print_success "Git $GIT_VERSION"
else
print_error "Git not found"
echo " Install: https://git-scm.com/downloads"
errors=$((errors + 1))
fi
# Check for Python 3.10+
PYTHON_CMD=$(get_python_cmd)
PLATFORM=$(get_platform)
if [ -n "$PYTHON_CMD" ]; then
# We know it's good because get_python_cmd validates it, but we run check again to get the version string
PY_VERSION=$(check_python_version "$PYTHON_CMD")
print_success "Python $PY_VERSION ($PYTHON_CMD)"
else
# Diagnostic: Check if they have ANY python, just too old
if command_exists python3; then
CURRENT_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null)
print_error "Found Python $CURRENT_VER, but 3.8+ is required"
else
print_error "Python 3.8+ not found"
fi
echo " Install: https://python.org/downloads or 'brew install python'"
errors=$((errors + 1))
fi
# Check for venv module
if [ -n "$PYTHON_CMD" ]; then
if $PYTHON_CMD -c "import venv" 2>/dev/null; then
print_success "Python venv module"
else
print_error "Python venv module not found"
echo " Install: sudo apt install python3-venv (Debian/Ubuntu)"
errors=$((errors + 1))
fi
fi
# Show Windows-specific guidance (contributed by @rnjema)
if [ "$PLATFORM" = "windows" ]; then
print_info "Windows detected - using Git Bash/WSL compatible mode"
fi
if [ $errors -gt 0 ]; then
echo ""
print_error "Missing prerequisites. Please fix the issues above."
exit 1
fi
}
check_existing_directory() {
if [ -d "$INSTALL_DIR" ]; then
print_error "Directory '$INSTALL_DIR' already exists"
echo " Remove it first: rm -rf $INSTALL_DIR"
echo " Or cd to a different location."
exit 1
fi
}
# ============================================================================
# Installation Steps
# ============================================================================
prompt_install_directory() {
# Non-interactive mode: use INSTALL_DIR as-is (from env var or default)
if [ -n "$NON_INTERACTIVE" ]; then
return
fi
# No TTY available: use defaults silently
if ! [ -t 0 ] && ! [ -e /dev/tty ]; then
return
fi
echo ""
echo -e "Where would you like to install Tiny${YELLOW}🔥Torch${NC}?"
echo -e " ${DIM}Press Enter for default: ${BOLD}$PWD/tinytorch${NC}"
echo ""
printf "Install directory [tinytorch]: "
read -r user_dir </dev/tty
if [ -n "$user_dir" ]; then
INSTALL_DIR="$user_dir"
fi
}
show_plan_and_confirm() {
echo ""
echo -e "This will create a ${CYAN}${INSTALL_DIR}${NC} folder here:"
echo -e " ${BOLD}$PWD/${INSTALL_DIR}${NC}"
echo ""
echo "What will be installed:"
echo -e " - Tiny${YELLOW}🔥Torch${NC} learning modules"
echo " - Python virtual environment (.venv/)"
echo " - tito CLI tool"
echo ""
echo -e "${DIM}Source: ${REPO_SHORT} (${BRANCH} branch)${NC}"
echo ""
}
do_install() {
echo ""
# -------------------------------------------------------------------------
# Step 1: Download from GitHub using sparse checkout
# This downloads only the tinytorch/ subdirectory, not the entire repo
# -------------------------------------------------------------------------
echo -e "${BLUE}[1/4]${NC} Downloading from GitHub..."
TEMP_DIR=$(mktemp -d)
git clone --depth 1 --filter=blob:none --sparse --branch "$BRANCH" \
"$REPO_URL" "$TEMP_DIR/repo" >/dev/null 2>&1 &
local clone_pid=$!
spin $clone_pid "Cloning repository..."
wait $clone_pid
local clone_status=$?
if [ $clone_status -ne 0 ]; then
print_error "Failed to download from GitHub"
echo " Check your internet connection and try again."
exit 1
fi
local original_dir="$PWD"
cd "$TEMP_DIR/repo"
git sparse-checkout set "$SPARSE_PATH" 2>/dev/null
# Capture commit hash for provenance tracking
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
cd "$original_dir"
# Move to final location
mv "$TEMP_DIR/repo/$SPARSE_PATH" "$INSTALL_DIR"
rm -rf "$TEMP_DIR"
TEMP_DIR=""
# -------------------------------------------------------------------------
# Clean up dev-only files that students don't need
#
# KEEP (students need these):
# src/ - Module source notebooks
# tinytorch/ - Package where student code goes
# tito/ - CLI tool source
# milestones/ - Historical ML recreations
# modules/ - Working directory (cleared, populated by tito)
# tests/ - Test suites for student code
# datasets/ - Sample datasets (tinydigits, tinytalks)
# bin/ - CLI entry point script
# requirements.txt, pyproject.toml - Package dependencies
# settings.ini - nbdev config (needed for exports)
# README.md, LICENSE - Documentation
#
# REMOVE (dev-only):
# -------------------------------------------------------------------------
rm -rf "$INSTALL_DIR/paper" \
"$INSTALL_DIR/instructor" \
"$INSTALL_DIR/site" \
"$INSTALL_DIR/scripts" \
"$INSTALL_DIR/tools" \
"$INSTALL_DIR/binder" \
"$INSTALL_DIR/etc" \
"$INSTALL_DIR/assignments" \
"$INSTALL_DIR/benchmark_results" \
"$INSTALL_DIR/.git-hooks" \
"$INSTALL_DIR/.claude" \
"$INSTALL_DIR/.cursor" \
"$INSTALL_DIR/.vscode" \
"$INSTALL_DIR/Makefile" \
"$INSTALL_DIR/activate.sh" \
"$INSTALL_DIR/setup-dev.sh" \
"$INSTALL_DIR/setup-environment.sh" \
"$INSTALL_DIR/CONTRIBUTING.md" \
"$INSTALL_DIR/INSTRUCTOR.md" \
"$INSTALL_DIR/MANIFEST.in" \
"$INSTALL_DIR/.pre-commit-config.yaml" \
"$INSTALL_DIR/.shared-ai-rules.md" \
"$INSTALL_DIR/.tinyrc" \
"$INSTALL_DIR/.editorconfig" \
"$INSTALL_DIR/.gitattributes" \
"$INSTALL_DIR/settings.json" \
"$INSTALL_DIR/.tinytorch" \
2>/dev/null || true
# Clear modules/ folder - students populate this via tito CLI exports
if [ -d "$INSTALL_DIR/modules" ]; then
find "$INSTALL_DIR/modules" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} + 2>/dev/null || true
fi
# Reset progress tracking - students start fresh
rm -f "$INSTALL_DIR/progress.json" 2>/dev/null || true
rm -rf "$INSTALL_DIR/.tito" 2>/dev/null || true
# Clear tinytorch/core/ implementation files - students build these
# Keep __init__.py files (package structure)
if [ -d "$INSTALL_DIR/tinytorch/core" ]; then
find "$INSTALL_DIR/tinytorch/core" -name "*.py" ! -name "__init__.py" -type f -delete 2>/dev/null || true
fi
print_success "Downloaded TinyTorch ${DIM}(${COMMIT_HASH})${NC}"
# -------------------------------------------------------------------------
# Step 2: Create Python virtual environment
# -------------------------------------------------------------------------
echo -e "${BLUE}[2/4]${NC} Creating Python environment..."
cd "$INSTALL_DIR"
# Use the detected 3.10+ command explicitly
$PYTHON_CMD -m venv .venv
# Activate venv (handle Windows Git Bash vs Unix)
if [ -f ".venv/Scripts/activate" ]; then
# Windows (Git Bash)
source .venv/Scripts/activate
else
# macOS/Linux
source .venv/bin/activate
fi
print_success "Created virtual environment using $PYTHON_CMD"
# -------------------------------------------------------------------------
# Step 3: Install dependencies
# Uses $PYTHON_CMD -m pip for reliability (contributed by @rnjema)
# -------------------------------------------------------------------------
echo -e "${BLUE}[3/4]${NC} Installing dependencies..."
# Upgrade pip first
$PYTHON_CMD -m pip install --upgrade pip -q 2>/dev/null &
local pip_pid=$!
spin $pip_pid "Upgrading pip..."
wait $pip_pid
# Install from requirements.txt
if [ -f "requirements.txt" ]; then
total_pkgs=$(grep -c -E "^[^#]" requirements.txt 2>/dev/null || echo "?")
$PYTHON_CMD -m pip install -r requirements.txt -q 2>/dev/null &
local req_pid=$!
spin $req_pid "Installing $total_pkgs packages..."
wait $req_pid
fi
# Install TinyTorch package in editable mode (includes tito CLI)
$PYTHON_CMD -m pip install -e . -q 2>/dev/null &
local tt_pid=$!
spin $tt_pid "Installing TinyTorch..."
wait $tt_pid
print_success "Installed dependencies"
# -------------------------------------------------------------------------
# Step 4: Verify installation
# -------------------------------------------------------------------------
echo -e "${BLUE}[4/4]${NC} Verifying installation..."
if command -v tito >/dev/null 2>&1; then
print_success "Verified tito CLI"
else
print_warning "Installation completed but tito not found in PATH"
echo " This is normal - activate the venv first."
fi
}
print_success_message() {
local install_path="$PWD"
# Determine correct activation command for the platform
local activate_cmd="source .venv/bin/activate"
if [ -f ".venv/Scripts/activate" ]; then
# Windows (Git Bash)
activate_cmd="source .venv/Scripts/activate"
fi
echo ""
echo -e "${GREEN}${NC} Tiny${YELLOW}🔥Torch${NC} installed successfully!"
echo ""
echo -e "${BOLD}Next steps:${NC}"
echo ""
echo -e " ${CYAN}cd $install_path${NC}"
echo -e " ${CYAN}$activate_cmd${NC}"
echo -e " ${CYAN}tito setup${NC}"
echo ""
echo -e "${BOLD}Then start building:${NC}"
echo ""
echo -e " ${CYAN}tito module start 01${NC}"
echo ""
echo -e "${DIM}Documentation: https://tinytorch.ai${NC}"
echo ""
}
# ============================================================================
# Main Entry Point
# ============================================================================
main() {
# Fetch version from GitHub (single source of truth: pyproject.toml via tags)
fetch_latest_version
print_banner
# Pre-flight checks
check_write_permission
check_not_in_venv
echo "Checking prerequisites..."
check_prerequisites
check_internet
# Ask where to install
prompt_install_directory
# Check directory doesn't exist (after user chooses)
check_existing_directory
# Show plan and confirm (skip in non-interactive mode)
if [ -z "$NON_INTERACTIVE" ] && { [ -t 0 ] || [ -e /dev/tty ]; }; then
show_plan_and_confirm
printf "Continue? [Y/n] "
read -r REPLY </dev/tty
if [[ $REPLY =~ ^[Nn]$ ]]; then
print_info "Installation cancelled"
exit 0
fi
fi
# Run installation
do_install
# Success message with next steps
print_success_message
}
main