.PHONY: help dep install update flame speedscope test has-staged-changes format lint pylint spelling fix-format fix-lint type-check check fix
MAKEFLAGS += --no-print-directory
TESTFLAGS ?= .
PYTHON ?= python
DEP ?=
FLAME ?=
SPEEDSCOPE ?=
LINESEP := ------------------------------------------------------------------
DEPENDENCIES := "ruff>=0.11.2" "pylint>=3.0.0" "mypy>=1.12.0" "codespell>=2.2.4" "pipdeptree" "pyinstrument"
ifneq ($(DEP),)
.DEFAULT_GOAL := dep
else ifneq ($(FLAME),)
.DEFAULT_GOAL := flame
else ifneq ($(SPEEDSCOPE),)
.DEFAULT_GOAL := speedscope
else
.DEFAULT_GOAL := help
endif
ifeq ($(filter cmd.exe sh.exe,$(SHELL)),$(SHELL))
START :=
END :=
DQ := "
NULL := NUL
BLANK := & echo.
FAIL_CMD := exit /b 1
CURL ?= curl.exe
else
START := "
END := "
DQ := \"
NULL := /dev/null
BLANK := ; echo ""
FAIL_CMD := exit 1
CURL ?= curl
endif
# Check last COMMITS commits if COMMITS > 0
COMMITS ?= 0
# If COMMITS > 0, treat it as "check last N commits"
ifneq ($(filter-out 0,$(COMMITS)),)
DIFF_OPTION := HEAD~$(COMMITS)..
else
DIFF_OPTION := --cached
endif
# Get changed files from git, then filter to get .py/.pyi via Python (cross-platform)
# Use -z flag (null-terminated) to avoid git quoting filenames
CHANGES_RAW := $(strip $(shell \
git diff -z --name-only $(DIFF_OPTION) --diff-filter=ACMR 2>$(NULL) | \
$(PYTHON) -c "import re;print(*(f for f in open(0).read().split('\0')if re.search(r'\.pyi?\Z',f)),sep='\n')" \
))
# Helper functions to check for quotes in paths and escape double quotes within a string
has-dquote = $(findstring ",$(1))
has-squote = $(findstring ',$(1))
escape-dquote = $(subst ",\",$(1))
# Helper function for handles spaces and quotes in filenames
# - If path contains double quote: use double quotes with escaped double quotes inside
# - Otherwise: use double quotes (handles spaces and single quotes safely)
define quote-path
$(if $(call has-dquote,$(1)), \
"$(call escape-dquote,$(1))" \
, \
"$(1)" \
)
endef
# Create a properly quoted list of files (handles spaces and quotes in filenames)
CHANGED_FILES := $(foreach file,$(CHANGES_RAW),$(call quote-path,$(file)))
# Sanitize output file for flame graph
FLAME_OUT := $(call quote-path,$(FLAME).html)
# Sanitize output file for speedscope
SPEEDSCOPE_OUT := $(call quote-path,$(SPEEDSCOPE).json)
# Detect uv
UV ?= $(strip $(shell uv --version >$(NULL) 2>&1 && echo yes || echo no))
ifeq ($(UV),yes)
RUN_CMD ?= uv run
INSTALL_CMD := uv pip install
else ifeq ($(UV),no)
RUN_CMD ?= $(PYTHON) -m
INSTALL_CMD := $(RUN_CMD) pip install
endif
help:
-@echo $(START)Usage: make [target] [COMMITS=N] [UV=yes|no]$(END)$(BLANK)
@echo - If COMMITS=N is specified and greater than 0, check Python files changed in last N commits
@echo $(START) Otherwise the currently staged changes are checked$(END)
@echo - If UV is set, it must be either yes or no, otherwise make will detect if uv is available$(BLANK)
@echo - You can also use this Makefile to check what packages depends on a specific package
@echo $(START) Example: make DEP=pydantic-core$(END)
@echo $(START) Syntax: make DEP=<package-name>$(END)$(BLANK)
@echo - This Makefile can be used to create a flame graph for a Python script
@echo $(START) Example: make FLAME=test-openai-emb.py$(END)
@echo $(START) Syntax: make FLAME=<script-name>$(END)$(BLANK)
@echo - This Makefile can be used to create a speedscope flame graph profile for a Python script
@echo $(START) Example: make SPEEDSCOPE=test-openai-emb.py$(END)
@echo $(START) Syntax: make SPEEDSCOPE=<script-name>$(END)
@echo $(START) View the profile at https://www.speedscope.app$(END)$(BLANK)
@echo Available targets:
@echo $(START) help - Show this help message$(END)
@echo $(START) install - Install dependencies via uv or pip$(END)
@echo $(START) update - Download latest version of this Makefile from gitcode.com/openJiuwen/agent-core$(END)
@echo $(START) test - Execute pytest, you can supply arguments via TESTFLAGS=$(DQ)...$(DQ)$(END)
@echo $(START) format - Check formatting of selected Python files via ruff$(END)
@echo $(START) lint - Check linting of selected Python files via ruff$(END)
@echo $(START) pylint - Check linting of selected Python files via pylint: more comprehensive$(END)
@echo $(START) spelling - Check spelling of selected Python files via codespell$(END)
@echo $(START) fix-format - Auto-fix formatting errors in selected Python files via ruff$(END)
@echo $(START) fix-lint - Auto-fix linting errors in selected Python files via ruff$(END)
@echo $(START) type-check - Type-check selected Python files via mypy$(END)
@echo $(START) check - Run all checks: format, spelling, lint, pylint$(END)
@echo $(START) fix - Run all auto-fixes: fix-lint, fix-format$(END)
dep:
@echo What packages depend on [$(DEP)]:
@$(RUN_CMD) pipdeptree --reverse --package $(DEP)
install:
ifeq ($(UV),yes)
@echo [Makefile] Installing dependencies via uv
@$(INSTALL_CMD) $(DEPENDENCIES)
else ifeq ($(UV),no)
@echo [Makefile] Installing dependencies via pip
@$(INSTALL_CMD) $(DEPENDENCIES)
else
@echo $(START)[Makefile] Unknown value for UV (yes/no): $(UV)$(END)
@$(FAIL_CMD)
endif
update:
@echo Downloading latest version of this Makefile from gitcode.com/openJiuwen/agent-core...
@echo NOTE: If this did not work, try running
@echo $(START) > make update CURL=path/to/your/curl_executable (curl.exe on Windows 10+)$(END)
@$(CURL) -fsSL https://raw.gitcode.com/openJiuwen/agent-core/raw/develop/Makefile -o Makefile
flame:
@echo Creating flame graph...
@$(RUN_CMD) pyinstrument -r html -o $(FLAME_OUT) $(call quote-path,$(FLAME))
@echo Flame graph created at $(FLAME_OUT)
speedscope:
@echo Creating speedscope profile...
@$(RUN_CMD) pyinstrument -r speedscope -o $(SPEEDSCOPE_OUT) $(call quote-path,$(SPEEDSCOPE))
@echo Speedscope profile created at $(SPEEDSCOPE_OUT)
@echo $(START)View it at https://www.speedscope.app$(END)
test:
@echo $(START)NOTE: To supply arguments to pytest (for example, to use pytest-xdist), try running$(END)
@echo $(START) > make test TESTFLAGS=$(DQ)...$(DQ)$(END)
@$(RUN_CMD) pytest $(TESTFLAGS)
# Sanity check - fails if there are no selected Python files
has-staged-changes:
ifeq ($(strip $(CHANGES_RAW)),)
@echo No Python files selected.
@echo NOTE: Make sure you have used git add first, or have set COMMITS to a positive integer. $(BLANK)
@echo $(LINESEP) $(BLANK)
@$(MAKE) help
@$(FAIL_CMD)
endif
format: has-staged-changes
-@$(RUN_CMD) ruff check --select I $(CHANGED_FILES)
-@$(RUN_CMD) ruff format --check $(CHANGED_FILES)
lint: has-staged-changes
-@$(RUN_CMD) ruff check --show-fixes $(CHANGED_FILES)
pylint: has-staged-changes
-@$(RUN_CMD) pylint $(CHANGED_FILES)
spelling: has-staged-changes
-@$(RUN_CMD) codespell $(CHANGED_FILES)
fix-format: has-staged-changes
-@$(RUN_CMD) ruff check --select I --fix $(CHANGED_FILES)
-@$(RUN_CMD) ruff format $(CHANGED_FILES)
fix-lint: has-staged-changes
-@$(RUN_CMD) ruff check --fix $(CHANGED_FILES)
type-check: has-staged-changes
-@$(RUN_CMD) mypy $(CHANGED_FILES)
check: has-staged-changes
@echo $(LINESEP)
@echo [Makefile] Checking code format...
-@$(MAKE) format COMMITS=$(COMMITS)
@echo $(LINESEP)
@echo [Makefile] Checking spelling...
-@$(MAKE) spelling COMMITS=$(COMMITS)
@echo $(LINESEP)
@echo [Makefile] Checking linting via ruff...
-@$(MAKE) lint COMMITS=$(COMMITS)
@echo $(LINESEP)
@echo [Makefile] Checking linting via pylint...
-@$(MAKE) pylint COMMITS=$(COMMITS)
fix: has-staged-changes
@echo $(LINESEP)
@echo [Makefile] Fixing linting via ruff...
-@$(MAKE) fix-lint COMMITS=$(COMMITS)
@echo $(LINESEP)
@echo [Makefile] Fixing code format...
-@$(MAKE) fix-format COMMITS=$(COMMITS)