-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathMakefile
More file actions
415 lines (355 loc) · 18.5 KB
/
Copy pathMakefile
File metadata and controls
415 lines (355 loc) · 18.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# Copyright 2026 Apple Inc.
#
# Use of this source code is governed by a BSD-3-Clause license that can
# be found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause
.PHONY: _maybe_patch_pyproject all api-list build build-dev check clean distclean distclean-all docs docs-clean docs-open env env-all env-docs env-highest-torch env-lowest-torch env-tutorial render-api-index set-auto-venv test test-cov test-fast test-highest-pytorch test-lowest-pytorch test-slow test-smoke test-tutorials version
SHELL := /bin/bash
# Directory holding this Makefile, derived from its own location so the same
# recipes work in both contexts:
#
# - Internal: `include external/Makefile` → `external/`
# - OSS: this file IS the root Makefile → `./`
#
# Captured at the very top with `:=` so it's evaluated before any later
# `-include` line (e.g. `-include .coreai-opt-venv`) appends to
# `$(MAKEFILE_LIST)` and shifts `$(lastword ...)`.
MAKEFILE_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
# Path prefix for the `scripts/` tree: `external/scripts` internally, `scripts` in OSS.
SCRIPTS := $(MAKEFILE_DIR)scripts
# The dev-tooling scripts import helpers from the `scripts/` tree, whose two halves
# live at the repo root (`scripts/`) and under `external/` (`external/scripts/`).
# PEP 420 merges them into one namespace package, so putting both dirs on PYTHONPATH
# lets the scripts resolve those imports — written as `external.scripts.*` (internal
# scripts) or plain `scripts.*` (build.py, which also runs post-export in OSS) —
# without per-file sys.path bootstrapping. `export` hands PYTHONPATH to every recipe
# subshell (uv run inherits it); tests get the same paths from pytest's `pythonpath`
# ini option. In the OSS mirror the second entry is just the repo root ($(CURDIR)/./).
export PYTHONPATH := $(CURDIR):$(CURDIR)/$(MAKEFILE_DIR)
# Tell coreai's runtime to skip the symbol-version check against the host's
# installed /System/Library/Frameworks/CoreAI.framework. Required when the
# precompiled coreai wheel was built against a newer SDK than what's on the
# host — without this, importing `coreai_torch` aborts at dlopen time with a
# Swift `Symbol not found` error. Override per-invocation with
# `make USE_LOCAL_COREAI=0 ...` if you want to exercise the system-framework
# code path.
USE_LOCAL_COREAI ?= 1
export USE_LOCAL_COREAI
# =============================================================================
# Configuration
# =============================================================================
# Virtual environment names for each environment type.
# When adding a new VENV_* variable, also add it to the env-all target's .coreai-opt-venv block.
VENV ?= .venv
VENV_DOCS ?= .venv-docs
VENV_HIGHEST_TORCH ?= .venv-highest-torch
VENV_LOWEST_TORCH ?= .venv-lowest-torch
VENV_TUTORIAL ?= .venv-tutorial
# The torch_2_* groups (pyproject.toml [dependency-groups]) currently at
# each end of the supported range. Bump these two lines — nothing else —
# when the project's torch version bounds change.
HIGHEST_TORCH_GROUP := torch_2_11
LOWEST_TORCH_GROUP := torch_2_8
# Torch dependency group (pyproject.toml [dependency-groups]) that every
# environment-building target (env, test, test-smoke, docs, ...) pins to.
TORCH_GROUP ?= $(HIGHEST_TORCH_GROUP)
export TORCH_GROUP
# Optional path to a pre-built distribution (wheel or sdist) for `test-smoke` to
# install instead of building from source; consumed by the nox smoke session via
# $SMOKE_TEST_DIST (empty = build from source). Exported like TORCH_GROUP so it
# reaches the nox subprocess.
SMOKE_TEST_DIST ?=
export SMOKE_TEST_DIST
# Documentation directory. Defaults to $(MAKEFILE_DIR)docs so the same recipe
# works in both contexts:
#
# - OSS: $(MAKEFILE_DIR) = ./ → DOCS_DIR = ./docs
# - Internal: $(MAKEFILE_DIR) = external/ → DOCS_DIR = external/docs
#
# The internal Makefile overrides this to `docs/` so `make docs` from the
# repo root builds the internal site (which mixes internal-only landing pages
# with symlinks to external/docs/src/).
DOCS_DIR ?= $(MAKEFILE_DIR)docs
# Load venv overrides (generated by env-* targets)
-include .coreai-opt-venv
# Python version (can be overridden: make env PYTHON_VERSION=3.12)
PYTHON_VERSION ?= 3.11
PYTHON_VERSION_BENCHMARKING ?= 3.11
SHELL_RC ?=
# Hook for downstream Makefiles to inject extra commands into env-all.
# Defaults to a no-op; the internal Makefile overrides this to write
# internal-only venv defaults.
ENV_ALL_EXTRAS ?= true
# Extra `uv pip install` arguments, applied to a venv after it is synced and to
# each nox session venv (see ci/nox/noxfile.py). Use it to swap a dependency
# version for one run without editing pyproject.toml or the lockfile — for
# example a scheduled job testing against newer upstream builds. Empty (the
# default) changes nothing.
POST_INSTALL_PIP_ARGS ?=
export POST_INSTALL_PIP_ARGS
# Re-apply POST_INSTALL_PIP_ARGS to the venv at $(1).
#
# setup_env.sh already does this, so only use it when a recipe installs more
# packages afterwards that could pull the old version back in (see env-all).
# Expands to `true` when unset. The empty check uses `$(if ...)` instead of a
# shell `[ -n "..." ]` test because the args contain their own quotes.
# Usage: $(call post_install_pip,VENV_PATH)
define post_install_pip
$(if $(POST_INSTALL_PIP_ARGS),echo "Applying POST_INSTALL_PIP_ARGS to $(1)" && source $(1)/bin/activate && uv pip install $(POST_INSTALL_PIP_ARGS),true)
endef
# Local wheelhouse for pre-release wheels not yet on an index.
# Exported so every recipe-level uv invocation (uv lock, uv sync, uv venv)
# resolves matching packages from disk without an index lookup. The wildcard
# guard keeps the OSS Makefile functional when the directory is absent — uv
# then falls back to its configured index.
ifneq ($(wildcard $(MAKEFILE_DIR)wheelhouse/.),)
export UV_FIND_LINKS := $(CURDIR)/$(MAKEFILE_DIR)wheelhouse
endif
# Lazy-evaluated default venv for set-auto-venv and distclean.
# Priority: explicit VENV= on command line > .coreai-opt-venv marker > default VENV
ifeq ($(origin VENV),command line)
DEFAULT_VENV = $(VENV)
else
DEFAULT_VENV = $(or $(shell sed -n 's/^DEFAULT_VENV *= *//p' .coreai-opt-venv 2>/dev/null),$(VENV))
endif
# Write .coreai-opt-venv marker file with the given venv path.
# Usage: $(call write_active_venv,VENV_PATH)
define write_active_venv
echo "# Generated by Makefile. Tracks the active venv for set-auto-venv and distclean." > .coreai-opt-venv && echo "DEFAULT_VENV = $(1)" >> .coreai-opt-venv
endef
# Script entrypoints reused by env-* and test-* targets.
SETUP_ENV = $(SCRIPTS)/make/setup_env.sh
RUN_TESTS = $(SCRIPTS)/make/run_tests.sh
# Auto-setup: ensure env is ready before running a target.
# Skips quickly if the venv already has the required deps.
ENSURE_ENV = $(SETUP_ENV) --ensure --python-version $(PYTHON_VERSION)
# Ensure env + activate venv in one step. Usage:
# $(call use_env,VENV_VAR) — for default dev env
# $(call use_env,VENV_VAR,--with-docs) — for env with extra groups
define use_env
$(ENSURE_ENV) --venv $($(1)) $(2) && source $($(1))/bin/activate
endef
# Run a command block quietly. Suppress output on success, replay to stderr on
# failure. Set QUIET=0 to disable suppression and run normally.
# Usage: @$(call run_quietly, echo "hello" && do_something)
#
# Note: This is a Make text macro. Make substitutes $(1) literally before the
# shell sees it, so compound commands (&&, ||, pipes) work naturally without
# bash -c wrapping.
define run_quietly
if [ "$(QUIET)" != "0" ]; then \
_log=$$(mktemp); \
if ($(1)) >"$$_log" 2>&1; then \
rm -f "$$_log"; \
else \
cat "$$_log" >&2; \
rm -f "$$_log"; \
exit 1; \
fi; \
else \
$(1); \
fi
endef
# Phony target — run as a prerequisite of every env-* target so PIN_DEPS /
# ALIAS_DEPS apply uniformly. Patches pyproject.toml in place when either
# variable is set; the `ifneq` makes the recipe entirely empty otherwise,
# so no shell is forked in the common path.
#
# Stdlib-only Python — safe to invoke before any venv exists. Patches are
# persistent; revert with `git checkout pyproject.toml uv.lock` after
# testing.
#
# Usage:
# make env PIN_DEPS="<dep>..." # append to base deps
# make env ALIAS_DEPS="<oss>=<spec>..." # substitute existing
# make env PIN_DEPS="..." ALIAS_DEPS="..." # both
_maybe_patch_pyproject:
ifneq ($(strip $(PIN_DEPS)$(ALIAS_DEPS)),)
@echo "Patching pyproject.toml with PIN_DEPS / ALIAS_DEPS..."
@python3 $(SCRIPTS)/patch_pyproject.py \
$(addprefix --pin-dep ,$(PIN_DEPS)) \
$(addprefix --alias-dep ,$(ALIAS_DEPS))
endif
# =============================================================================
# Default
# =============================================================================
# Default target - run full workflow
all: clean distclean-all env-all check test-lowest-pytorch test-highest-pytorch build-dev
# =============================================================================
# Environment Setup
# =============================================================================
# Set up development environment
env: _maybe_patch_pyproject
@$(SETUP_ENV) --venv $(VENV) --python-version $(PYTHON_VERSION)
@$(call write_active_venv,$(VENV))
# Set up development environment with latest supported PyTorch version
env-highest-torch: _maybe_patch_pyproject
@TORCH_GROUP=$(HIGHEST_TORCH_GROUP) $(SETUP_ENV) --venv $(VENV_HIGHEST_TORCH) --python-version $(PYTHON_VERSION)
@$(call write_active_venv,$(VENV_HIGHEST_TORCH))
# Set up development environment with lowest supported PyTorch version
env-lowest-torch: _maybe_patch_pyproject
@TORCH_GROUP=$(LOWEST_TORCH_GROUP) $(SETUP_ENV) --venv $(VENV_LOWEST_TORCH) --python-version $(PYTHON_VERSION)
@$(call write_active_venv,$(VENV_LOWEST_TORCH))
# Set up environment for running tutorials (quantization notebook)
env-tutorial: _maybe_patch_pyproject
@$(SETUP_ENV) --venv $(VENV_TUTORIAL) --python-version $(PYTHON_VERSION) --with-tutorial
@$(call write_active_venv,$(VENV_TUTORIAL))
# Set up development environment with all dependencies
env-all: _maybe_patch_pyproject
@$(SETUP_ENV) --venv $(VENV) --python-version $(PYTHON_VERSION) --all-groups
@$(call write_active_venv,$(VENV))
@$(ENV_ALL_EXTRAS)
@$(call post_install_pip,$(VENV))
# =============================================================================
# Build
# =============================================================================
# Build the canonical, publishable distribution (wheel + sdist): the on-tree
# version with any `.dev` suffix stripped (e.g. 0.2.2.dev0 -> 0.2.2), via
# `uv build --no-sources`. `--no-sources` ignores [tool.uv.sources], so the
# artifact doesn't depend on uv-specific index overrides — the recommended way
# to build for publication. This is what the release workflow runs. Routed
# through build.py (like build-dev) so both targets share one code path; set
# COREAI_OPT_VERSION_EXTENSION to insert an extra release segment (see
# RELEASE.md).
build:
@$(call use_env,VENV) && uv run --no-sync --active python $(SCRIPTS)/make/build.py --no-sources
# Build a development distribution with build.py: the release base with a
# unique, timestamped PEP 440 dev suffix (e.g. 0.2.2.dev202607231430+abc1234).
# Used by contributors, the smoke tests, and the nightly pipeline. Set
# DEV_VERSION=... to use an exact version instead.
build-dev:
@$(call use_env,VENV) && uv run --no-sync --active python $(SCRIPTS)/make/build.py --dev
# =============================================================================
# Code Quality
# =============================================================================
# Print public API surface (symbols declared in __all__ across all public packages).
# Pass MODULE= to inspect a single module: make api-list MODULE=coreai_opt.quantization.spec.spec
api-list:
@$(call use_env,VENV) && uv run --no-sync --active python $(SCRIPTS)/make/print_api_list.py $(MODULE)
# Run linting and type checking.
check:
@$(call use_env,VENV) && \
echo "Running linting and formatting checks..." && \
uv run --no-sync --active pre-commit run --all-files && \
echo "All checks passed!"
# =============================================================================
# Testing
# =============================================================================
# Run tests (pass PYTEST_ARGS for custom flags, e.g., make test PYTEST_ARGS="--cov")
test:
@$(call use_env,VENV) && $(RUN_TESTS) $(PYTEST_ARGS)
# Run tests with coverage
test-cov:
@$(MAKE) test PYTEST_ARGS="--cov"
# Run fast tests only
test-fast:
@$(MAKE) test PYTEST_ARGS="--marker 'not slow'"
# Run slow tests only
test-slow:
@$(MAKE) test PYTEST_ARGS="--marker slow"
# Run smoke tests only (pass PYTEST_ARGS for custom flags, e.g., make test-smoke PYTEST_ARGS="--junitxml=results.xml").
# Pass TORCH_GROUP to smoke test against a specific torch version (default: HIGHEST_TORCH_GROUP).
# Pass SMOKE_TEST_DIST=<path to a .whl or .tar.gz> to smoke test a pre-built
# distribution instead of building one from source (used by the release
# workflow to test the exact artifact being published).
test-smoke:
@$(call use_env,VENV) && \
echo "Running smoke tests..." && \
uv run --no-sync --active nox -f $(MAKEFILE_DIR)ci/nox/noxfile.py -s smoke_tests -- $(PYTEST_ARGS) && \
echo "All smoke tests passed!"
# Run tests on lowest supported PyTorch version (pass PYTEST_ARGS for custom flags).
# TORCH_GROUP is already exported, so setting it per target is enough for
# use_env to pick the right torch build. Use `=`, not `:=`: an including
# Makefile may change HIGHEST_TORCH_GROUP after this file is read.
test-lowest-pytorch: TORCH_GROUP = $(LOWEST_TORCH_GROUP)
test-lowest-pytorch:
@echo "Running tests on lowest PyTorch version supported..."
@$(call use_env,VENV_LOWEST_TORCH) && \
echo "Testing with lowest supported PyTorch versions" && \
uv run --no-sync --active python $(SCRIPTS)/make/log_versions.py && \
$(RUN_TESTS) $(PYTEST_ARGS) && \
echo "All tests passed!"
# Run tests on highest supported PyTorch version (pass PYTEST_ARGS for custom flags)
test-highest-pytorch: TORCH_GROUP = $(HIGHEST_TORCH_GROUP)
test-highest-pytorch:
@echo "Running tests on highest PyTorch version supported..."
@$(call use_env,VENV_HIGHEST_TORCH) && \
echo "Testing with latest supported PyTorch versions" && \
uv run --no-sync --active python $(SCRIPTS)/make/log_versions.py && \
$(RUN_TESTS) $(PYTEST_ARGS) && \
echo "All tests passed!"
# Run tutorial notebook tests
test-tutorials:
@$(call use_env,VENV_TUTORIAL,--with-tutorial --with-test) && \
echo "Running tutorial notebook tests..." && \
$(RUN_TESTS) --path $(DOCS_DIR)/tests/test_tutorials.py $(PYTEST_ARGS) && \
echo "All tutorial tests passed!"
# =============================================================================
# Maintenance
# =============================================================================
# Clean up build artifacts
clean:
@$(SCRIPTS)/make/clean.sh
# Deep clean (removes current active venv, uv.lock, and .envrc; switches back to .venv if available)
distclean:
@$(SCRIPTS)/make/distclean.sh
# Deep clean ALL local virtual environments (removes all .venv, .my_env, etc.)
distclean-all:
@$(SCRIPTS)/make/distclean.sh all
# Set up automatic virtual environment activation with direnv.
# Auto-detects venv when combined with env-* targets:
# make env set-auto-venv - configures direnv for .venv
# make set-auto-venv VENV=.my_env - explicit override
# make set-auto-venv - uses last env-* venv, or default .venv
set-auto-venv:
@$(SCRIPTS)/make/set_auto_venv.sh $(DEFAULT_VENV) $(SHELL_RC)
# Show the development version carried on the tree (e.g. 0.2.2.dev0), including
# any COREAI_OPT_VERSION_EXTENSION (e.g. 0.2.2.1.dev0). Reads _about.py as plain
# text, so no venv is needed — but `uv run --no-project` is still what guarantees
# a >= 3.11 interpreter (a bare `python3` is 3.9 on stock macOS, and `python` may
# not exist at all) without requiring `make env` first.
version:
@uv run --no-config --no-project --python '>=3.11' $(SCRIPTS)/make/print_version.py
# =============================================================================
# Documentation
# =============================================================================
# Set up development environment with docs
env-docs: _maybe_patch_pyproject
@$(SETUP_ENV) --venv $(VENV_DOCS) --python-version $(PYTHON_VERSION) --with-docs
@$(call write_active_venv,$(VENV_DOCS))
# Build documentation (pass QUIET=0 to show output)
# Use -E so Sphinx re-reads all sources, ensuring extension changes
# (e.g. source-read hooks) take effect without wiping the build directory.
docs:
ifndef _QUIET_HEADER
@echo ""
@echo "════════════════════════════════════════════════════════════════════"
@echo "▶ Building documentation [internal] ($(DOCS_DIR))"
@echo "════════════════════════════════════════════════════════════════════"
@echo ""
endif
@echo "==> [1/5] Ensuring @mermaid-js/mermaid-cli" && $(SCRIPTS)/ensure_npm_package.sh "@mermaid-js/mermaid-cli" "mmdc"
@echo "==> [2/5] Ensuring Chrome for mmdc" && $(SCRIPTS)/ensure_mmdc_chrome.sh
@echo "==> [3/5] Ensuring pandoc" && $(SCRIPTS)/ensure_pandoc.sh
@echo "==> [4/5] Setting up docs environment" && \
$(call use_env,VENV_DOCS,--with-docs) && \
echo "==> [5/5] Building documentation" && \
cd $(DOCS_DIR) && uv run --no-sync --active sphinx-build -E -b html src build/html
ifndef _DOCS_ALL
@echo ""
@echo "════════════════════════════════════════════════════════════════════"
@echo "Documentation built successfully! Open $(DOCS_DIR)/build/html/index.html"
endif
# Remove documentation build artifacts and autosummary-generated stubs
docs-clean:
@rm -rf $(DOCS_DIR)/build $(DOCS_DIR)/src/api/generated
# Regenerate docs/src/api/index.md from the package tree.
#
# Runs the same generator `make docs` invokes during the Sphinx build, so the
# API index can be refreshed on its own using the base dev env.
render-api-index:
@$(call use_env,VENV) && uv run --no-sync --active python $(MAKEFILE_DIR)docs/scripts/generate_api_index.py
# Build and open documentation in browser
# Uses --serve so the docs are loaded over HTTP, not file:// — required for
# the Copy page button (and any other feature using fetch()/clipboard APIs).
docs-open: docs
@$(DOCS_DIR)/scripts/open_in_browser.py --serve $(DOCS_DIR)/build/html/index.html