Skip to content

Commit 2732a26

Browse files
authored
Merge pull request #1522 from harvard-edge/release/mlsysim-v0.1.1
mlsysim: v0.1.1 + automated PyPI publish workflow
2 parents ae73eea + 2992a12 commit 2732a26

8 files changed

Lines changed: 430 additions & 91 deletions

File tree

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
name: '🧮 MLSYSIM · 📦 PyPI Publish'
2+
3+
# =============================================================================
4+
# MLSYSIM — PyPI Release
5+
# =============================================================================
6+
#
7+
# Builds and uploads the mlsysim Python package to PyPI when a tag matching
8+
# `mlsysim-v*` is pushed. Uses Trusted Publishing (OIDC), so no PyPI API
9+
# token is stored anywhere in the repo or in GitHub Secrets.
10+
#
11+
# Flow:
12+
# 1. VERIFY — tag format, version coherence across files, CHANGELOG
13+
# entry, reachable from dev
14+
# 2. TEST — full pytest suite in a clean venv
15+
# 3. BUILD — python -m build → wheel + sdist
16+
# 4. CHECK — twine check on both artifacts
17+
# 5. PUBLISH — upload to PyPI via pypa/gh-action-pypi-publish (OIDC)
18+
# 6. RELEASE — create GitHub Release with wheel + sdist attached
19+
# 7. DOCS — dispatch mlsysim-publish-live.yml to refresh docs site
20+
#
21+
# Triggers:
22+
# - push: tags matching 'mlsysim-v*'
23+
# - workflow_dispatch: manual trigger (for re-running docs or release-only)
24+
#
25+
# Publishes to: https://pypi.org/project/mlsysim/
26+
# Secrets: GITHUB_TOKEN (for GH Release + docs trigger)
27+
# No PyPI secret required (Trusted Publishing via OIDC)
28+
# Environment: pypi-mlsysim (for deployment tracking + protection rules)
29+
#
30+
# Related:
31+
# - mlsysim-validate-dev.yml — Test & docs validation (runs on PRs)
32+
# - mlsysim-publish-live.yml — Docs site publish (dispatched by this)
33+
#
34+
# Trusted Publishing setup (one-time, via pypi.org web UI):
35+
# https://pypi.org/manage/account/publishing/
36+
# Project: mlsysim
37+
# Owner: harvard-edge
38+
# Repository: cs249r_book
39+
# Workflow: mlsysim-pypi-publish.yml
40+
# Environment: pypi-mlsysim
41+
#
42+
# =============================================================================
43+
44+
on:
45+
push:
46+
tags:
47+
- 'mlsysim-v*'
48+
workflow_dispatch:
49+
inputs:
50+
skip_pypi:
51+
description: 'Skip the PyPI upload (useful for re-running docs + release)'
52+
required: false
53+
type: boolean
54+
default: false
55+
56+
permissions:
57+
contents: write # needed to create the GitHub Release
58+
id-token: write # needed for Trusted Publishing (OIDC)
59+
actions: write # needed to dispatch mlsysim-publish-live.yml
60+
61+
concurrency:
62+
group: mlsysim-pypi-${{ github.ref }}
63+
cancel-in-progress: false # never cancel a publish in flight
64+
65+
jobs:
66+
# ===========================================================================
67+
# Stage 1: Verify coherence before doing any irreversible work
68+
# ===========================================================================
69+
verify:
70+
name: '🔍 Verify Version Coherence'
71+
runs-on: ubuntu-latest
72+
outputs:
73+
version: ${{ steps.extract.outputs.version }}
74+
tag: ${{ steps.extract.outputs.tag }}
75+
steps:
76+
- name: 📥 Checkout
77+
uses: actions/checkout@v6
78+
with:
79+
fetch-depth: 0 # need full history to verify tag reachability from dev
80+
81+
- name: 🏷️ Extract version from tag
82+
id: extract
83+
run: |
84+
TAG="${GITHUB_REF_NAME}"
85+
if [[ ! "$TAG" =~ ^mlsysim-v[0-9]+\.[0-9]+\.[0-9]+(.*)?$ ]]; then
86+
echo "::error::Tag '$TAG' does not match expected pattern 'mlsysim-vX.Y.Z'"
87+
exit 1
88+
fi
89+
VERSION="${TAG#mlsysim-v}"
90+
echo "tag=$TAG" | tee -a "$GITHUB_OUTPUT"
91+
echo "version=$VERSION" | tee -a "$GITHUB_OUTPUT"
92+
93+
- name: 🔢 Verify pyproject.toml version matches tag
94+
run: |
95+
PKG_VERSION=$(grep -E '^version = ' mlsysim/pyproject.toml | head -1 | cut -d'"' -f2)
96+
EXPECTED="${{ steps.extract.outputs.version }}"
97+
if [ "$PKG_VERSION" != "$EXPECTED" ]; then
98+
echo "::error::pyproject.toml version '$PKG_VERSION' does not match tag version '$EXPECTED'"
99+
exit 1
100+
fi
101+
echo "✅ pyproject.toml version ($PKG_VERSION) matches tag"
102+
103+
- name: 🔢 Verify __init__.py version matches tag
104+
run: |
105+
PKG_VERSION=$(grep -E '^__version__ = ' mlsysim/mlsysim/__init__.py | head -1 | cut -d'"' -f2)
106+
EXPECTED="${{ steps.extract.outputs.version }}"
107+
if [ "$PKG_VERSION" != "$EXPECTED" ]; then
108+
echo "::error::mlsysim/__init__.py __version__ ('$PKG_VERSION') does not match tag ('$EXPECTED')"
109+
exit 1
110+
fi
111+
echo "✅ __init__.py version ($PKG_VERSION) matches tag"
112+
113+
- name: 🔢 Verify CITATION.cff version matches tag
114+
run: |
115+
CFF_VERSION=$(grep -E '^version:' mlsysim/CITATION.cff | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
116+
EXPECTED="${{ steps.extract.outputs.version }}"
117+
if [ "$CFF_VERSION" != "$EXPECTED" ]; then
118+
echo "::error::CITATION.cff version ('$CFF_VERSION') does not match tag ('$EXPECTED')"
119+
exit 1
120+
fi
121+
echo "✅ CITATION.cff version ($CFF_VERSION) matches tag"
122+
123+
- name: 📝 Verify CHANGELOG has entry for this version
124+
run: |
125+
EXPECTED="${{ steps.extract.outputs.version }}"
126+
if ! grep -qE "^## v${EXPECTED}[[:space:]]" mlsysim/CHANGELOG.md; then
127+
echo "::error::CHANGELOG.md is missing an entry for v${EXPECTED}"
128+
echo "Expected a heading like: '## v${EXPECTED} (YYYY-MM-DD)'"
129+
exit 1
130+
fi
131+
echo "✅ CHANGELOG entry present for v${EXPECTED}"
132+
133+
- name: 🌿 Verify tag is reachable from dev
134+
run: |
135+
# Published packages should come from code that has been merged to dev.
136+
# This prevents accidentally tagging a stale branch or feature branch.
137+
if ! git merge-base --is-ancestor "$GITHUB_SHA" "origin/dev" 2>/dev/null; then
138+
echo "::warning::Tagged commit is not an ancestor of origin/dev."
139+
echo "This is allowed but unusual. Continuing anyway (publish discipline rests on reviewer)."
140+
else
141+
echo "✅ Tagged commit is reachable from origin/dev"
142+
fi
143+
144+
# ===========================================================================
145+
# Stage 2: Full test suite in a clean environment
146+
# ===========================================================================
147+
test:
148+
name: '🧪 Test Suite'
149+
needs: verify
150+
runs-on: ubuntu-latest
151+
steps:
152+
- name: 📥 Checkout
153+
uses: actions/checkout@v6
154+
155+
- name: 🐍 Setup Python
156+
uses: actions/setup-python@v6
157+
with:
158+
python-version: '3.11'
159+
160+
- name: 📦 Install package with dev extras
161+
working-directory: mlsysim
162+
run: pip install ".[dev]"
163+
164+
- name: 🧪 Run tests
165+
working-directory: mlsysim
166+
run: pytest tests/ -v --tb=short
167+
168+
# ===========================================================================
169+
# Stage 3: Build wheel + sdist (reproducible, clean)
170+
# ===========================================================================
171+
build:
172+
name: '🔨 Build Distribution'
173+
needs: [verify, test]
174+
runs-on: ubuntu-latest
175+
outputs:
176+
wheel_name: ${{ steps.names.outputs.wheel }}
177+
sdist_name: ${{ steps.names.outputs.sdist }}
178+
steps:
179+
- name: 📥 Checkout
180+
uses: actions/checkout@v6
181+
182+
- name: 🐍 Setup Python
183+
uses: actions/setup-python@v6
184+
with:
185+
python-version: '3.11'
186+
187+
- name: 📦 Install build tools
188+
run: pip install build twine
189+
190+
- name: 🔨 Build wheel + sdist
191+
working-directory: mlsysim
192+
run: python -m build
193+
194+
- name: 🔍 twine check
195+
working-directory: mlsysim
196+
run: twine check dist/*
197+
198+
- name: 📛 Capture artifact names
199+
id: names
200+
working-directory: mlsysim
201+
run: |
202+
echo "wheel=$(ls dist/*.whl)" >> "$GITHUB_OUTPUT"
203+
echo "sdist=$(ls dist/*.tar.gz)" >> "$GITHUB_OUTPUT"
204+
205+
- name: 📤 Upload distributions as workflow artifact
206+
uses: actions/upload-artifact@v4
207+
with:
208+
name: mlsysim-dist-${{ needs.verify.outputs.version }}
209+
path: mlsysim/dist/
210+
if-no-files-found: error
211+
retention-days: 90
212+
213+
# ===========================================================================
214+
# Stage 4: Publish to PyPI via Trusted Publishing (OIDC)
215+
# ===========================================================================
216+
# No API token is used. The pypa/gh-action-pypi-publish action requests an
217+
# OIDC token from GitHub, which PyPI then validates against the trusted
218+
# publisher configuration. If this step fails with an OIDC error, it means
219+
# Trusted Publishing has not been set up on pypi.org for this project/workflow.
220+
# ===========================================================================
221+
publish-pypi:
222+
name: '📦 Publish to PyPI'
223+
needs: [verify, test, build]
224+
if: ${{ !inputs.skip_pypi }}
225+
runs-on: ubuntu-latest
226+
environment:
227+
name: pypi-mlsysim
228+
url: https://pypi.org/project/mlsysim/${{ needs.verify.outputs.version }}/
229+
steps:
230+
- name: 📥 Download build artifacts
231+
uses: actions/download-artifact@v4
232+
with:
233+
name: mlsysim-dist-${{ needs.verify.outputs.version }}
234+
path: dist/
235+
236+
- name: 📦 Upload to PyPI
237+
uses: pypa/gh-action-pypi-publish@release/v1
238+
with:
239+
packages-dir: dist/
240+
# Default repository URL is https://upload.pypi.org/legacy/ — correct
241+
# for production PyPI. Trusted Publishing is configured per-project
242+
# at https://pypi.org/manage/account/publishing/.
243+
verbose: true
244+
245+
# ===========================================================================
246+
# Stage 5: Create GitHub Release with artifacts attached
247+
# ===========================================================================
248+
github-release:
249+
name: '🏷️ GitHub Release'
250+
needs: [verify, build, publish-pypi]
251+
if: always() && needs.build.result == 'success' && (needs.publish-pypi.result == 'success' || inputs.skip_pypi)
252+
runs-on: ubuntu-latest
253+
steps:
254+
- name: 📥 Checkout
255+
uses: actions/checkout@v6
256+
257+
- name: 📥 Download build artifacts
258+
uses: actions/download-artifact@v4
259+
with:
260+
name: mlsysim-dist-${{ needs.verify.outputs.version }}
261+
path: dist/
262+
263+
- name: 📝 Resolve release notes file
264+
id: notes
265+
run: |
266+
VERSION="${{ needs.verify.outputs.version }}"
267+
NOTES_FILE="mlsysim/RELEASE_NOTES_${VERSION}.md"
268+
if [ -f "$NOTES_FILE" ]; then
269+
echo "file=$NOTES_FILE" >> "$GITHUB_OUTPUT"
270+
echo "✅ Using $NOTES_FILE"
271+
else
272+
# Fallback: extract CHANGELOG entry for this version
273+
echo "file=" >> "$GITHUB_OUTPUT"
274+
echo "ℹ️ $NOTES_FILE not found; GitHub Release will use auto-generated notes from CHANGELOG.md"
275+
fi
276+
277+
- name: 🏷️ Create GitHub Release
278+
uses: softprops/action-gh-release@v2
279+
with:
280+
tag_name: ${{ needs.verify.outputs.tag }}
281+
name: 'MLSys·im ${{ needs.verify.outputs.version }}'
282+
body_path: ${{ steps.notes.outputs.file }}
283+
files: |
284+
dist/*.whl
285+
dist/*.tar.gz
286+
generate_release_notes: ${{ steps.notes.outputs.file == '' }}
287+
fail_on_unmatched_files: true
288+
289+
# ===========================================================================
290+
# Stage 6: Trigger docs redeploy so mlsysbook.ai/mlsysim reflects new version
291+
# ===========================================================================
292+
docs-redeploy:
293+
name: '📚 Trigger Docs Redeploy'
294+
needs: [verify, publish-pypi]
295+
if: ${{ needs.publish-pypi.result == 'success' }}
296+
runs-on: ubuntu-latest
297+
steps:
298+
- name: 📚 Dispatch mlsysim-publish-live.yml on dev
299+
env:
300+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
301+
run: |
302+
gh workflow run mlsysim-publish-live.yml \
303+
--repo ${{ github.repository }} \
304+
--ref dev
305+
echo "✅ Dispatched docs redeploy on dev"

mlsysim/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## v0.1.1 (2026-04-24)
4+
5+
**Metadata-only patch release.** No code or API changes; safe drop-in
6+
replacement for 0.1.1.
7+
8+
- Corrected paper title in citations: now reads "MLSys·im:
9+
First-Principles Infrastructure Modeling for Machine Learning
10+
Systems" (was "A Composable Analytical Framework for Machine Learning
11+
Systems"). Updated in `CITATION.cff`, the BibTeX snippet in
12+
`README.md`, and the reference in `mlsysim/core/walls.py`.
13+
314
## v0.1.0 (2026-04-01)
415

516
**Initial release** of MLSysim — the first-principles analytical modeling engine for ML systems.

mlsysim/CITATION.cff

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
cff-version: 1.2.0
22
message: "If you use mlsysim in your research or teaching, please cite it as below."
3-
title: "MLSysim: A Composable Analytical Framework for Machine Learning Systems"
3+
title: "MLSys·im: First-Principles Infrastructure Modeling for Machine Learning Systems"
44
type: software
55
authors:
66
- family-names: "Janapa Reddi"
77
given-names: "Vijay"
88
orcid: "https://orcid.org/0000-0002-5005-2564"
99
affiliation: "Harvard University"
10-
version: "0.1.0"
11-
date-released: "2026-04-01"
10+
version: "0.1.1"
11+
date-released: "2026-04-24"
1212
license: "Apache-2.0"
1313
url: "https://mlsysbook.ai/mlsysim"
1414
repository-code: "https://github.com/harvard-edge/cs249r_book"

mlsysim/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ If you use mlsysim in your research or teaching, please cite:
157157
```bibtex
158158
@software{mlsysim2026,
159159
author = {Janapa Reddi, Vijay},
160-
title = {{MLSysim}: A Composable Analytical Framework for Machine Learning Systems},
160+
title = {{MLSys$\cdot$im}: First-Principles Infrastructure Modeling for Machine Learning Systems},
161161
year = {2026},
162162
url = {https://mlsysbook.ai/mlsysim},
163-
version = {0.1.0},
163+
version = {0.1.1},
164164
institution = {Harvard University}
165165
}
166166
```

0 commit comments

Comments
 (0)