Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ repos:
language: system
entry: end-of-file-fixer
types: [text]
exclude: ^\.codegenius/learnings\.md$
exclude: ^\.codegenius/

- id: check-yaml
name: check yaml
Expand Down Expand Up @@ -174,7 +174,7 @@ repos:
language: system
entry: mdformat --number
files: \.(md)$
exclude: ^(CHANGELOG\.md|\.codegenius/learnings\.md)$
exclude: ^(CHANGELOG\.md$|\.codegenius/)
args: [
--number, # Force 1., 2., 3. instead of all 1.
--wrap=keep, # Preserve existing line breaks
Expand Down Expand Up @@ -255,7 +255,7 @@ repos:
language: system
entry: pymarkdown --disable-rules MD013,MD024 scan
files: \.(md)$
exclude: ^\.codegenius/learnings\.md$
exclude: ^\.codegenius/

# ----------------------------------------------------------------------------
# 3.5 Lint Shell Script (bashate)
Expand Down
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
- `pytest -n auto path/to/test.py` — run a test file
- `pytest path/to/test.py::test_name` — run a single test

## uv usage

Always pass `--no-sync` to `uv run`: `uv run --no-sync --active …`.

`uv run` implicitly syncs the active project to its default-groups before running, which re-resolves dependencies and can clobber a venv's group-pinned packages — e.g. the torch pin in `.venv-lowest-torch`/`.venv-highest-torch` gets re-anchored back to the default torch. Our Make targets always prepare the environment first via `use_env`/`setup_env.sh`, so by the time `uv run` executes the deps are already correct. A `uv run` invocation should be a read-only run of a command in that prepared env, never a dependency mutation — `--no-sync` enforces that.

## Editing Guidelines

- Use `@path` to reference small files (loaded into every session automatically).
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Before pushing your changes, run these locally:
- `make check` — full lint and `mypy` type-check pass
- `make test` — full test suite (parallelized with `pytest-xdist`)
- `make test-fast` — excludes tests marked `@pytest.mark.slow` for quicker iteration
- `make test-smoke` — builds the package, installs it into a clean environment, and verifies that imports plus basic quantization and palettization work end to end

A clean `make check` and `make test` are required before a pull request will be reviewed.

Expand Down
46 changes: 25 additions & 21 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
# 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 check clean distclean distclean-all docs docs-clean docs-open env env-all env-docs env-highest-torch env-latest-coreai env-tutorial set-auto-venv test test-coreai-compression test-cov test-export test-fast test-highest-pytorch test-lowest-pytorch test-slow version
.PHONY: _maybe_patch_pyproject all api-list build check clean distclean distclean-all docs docs-clean docs-open env env-all env-docs env-highest-torch env-tutorial 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:
Expand Down Expand Up @@ -161,7 +163,7 @@ endif
# =============================================================================

# Default target - run full workflow
all: clean distclean-all env-all check test-export test-lowest-pytorch test-highest-pytorch build
all: clean distclean-all env-all check test-lowest-pytorch test-highest-pytorch build

# =============================================================================
# Environment Setup
Expand All @@ -177,10 +179,6 @@ env-highest-torch: _maybe_patch_pyproject
@$(SETUP_ENV) --venv $(VENV_HIGHEST_TORCH) --python-version $(PYTHON_VERSION) --with-highest_tested_torch
@$(call write_active_venv,$(VENV_HIGHEST_TORCH))

# Set up development environment with latest CoreAI for export testing
env-latest-coreai: _maybe_patch_pyproject
@$(SETUP_ENV) --venv .venv_latest_coreai --python-version $(PYTHON_VERSION) --with-latest-coreai --without-stable-coreai

# Set up environment for running tutorials (quantization notebook)
env-tutorial: _maybe_patch_pyproject
@$(SETUP_ENV) --venv $(VENV_TUTORIAL) --python-version $(PYTHON_VERSION) --with-tutorial
Expand All @@ -198,7 +196,7 @@ env-all: _maybe_patch_pyproject

# Build package
build:
@$(call use_env,VENV) && uv run --active python $(SCRIPTS)/make/build.py
@$(call use_env,VENV) && uv run --no-sync --active python $(SCRIPTS)/make/build.py

# =============================================================================
# Code Quality
Expand All @@ -207,13 +205,13 @@ build:
# 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 --active python $(SCRIPTS)/make/print_api_list.py $(MODULE)
@$(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 --active pre-commit run --all-files && \
uv run --no-sync --active pre-commit run --all-files && \
echo "All checks passed!"

# =============================================================================
Expand All @@ -236,20 +234,19 @@ test-fast:
test-slow:
@$(MAKE) test PYTEST_ARGS="--marker slow"

# Run export tests with latest CoreAI (pass PYTEST_ARGS for custom flags)
test-export: env-latest-coreai
@$(SCRIPTS)/make/run_tests_on_latest_coreai.sh --path tests/export/ $(PYTEST_ARGS)

# Run coreai compression tests with latest CoreAI (pass PYTEST_ARGS for custom flags)
test-coreai-compression: env-latest-coreai
@$(SCRIPTS)/make/run_tests_on_latest_coreai.sh --path tests/coreai_utils/ $(PYTEST_ARGS)
# Run smoke tests only (pass PYTEST_ARGS for custom flags, e.g., make test-smoke PYTEST_ARGS="--junitxml=results.xml").
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)
test-lowest-pytorch:
@echo "Running tests on lowest PyTorch version supported..."
@$(call use_env,VENV_LOWEST_TORCH,--with-lowest_tested_torch --without-stable-coreai) && \
@$(call use_env,VENV_LOWEST_TORCH,--with-lowest_tested_torch) && \
echo "Testing with lowest supported PyTorch versions" && \
uv run --active python $(SCRIPTS)/make/log_versions.py && \
uv run --no-sync --active python $(SCRIPTS)/make/log_versions.py && \
$(RUN_TESTS) $(PYTEST_ARGS) && \
echo "All tests passed!"

Expand All @@ -258,10 +255,17 @@ test-highest-pytorch:
@echo "Running tests on highest PyTorch version supported..."
@$(call use_env,VENV_HIGHEST_TORCH,--with-highest_tested_torch) && \
echo "Testing with latest supported PyTorch versions" && \
uv run --active python $(SCRIPTS)/make/log_versions.py && \
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
# =============================================================================
Expand All @@ -288,7 +292,7 @@ set-auto-venv:

# Show current version
version:
@python -c "exec(open('./src/coreai_opt/_about.py').read()); print(__version__)"
@python -c "exec(open('$(MAKEFILE_DIR)src/coreai_opt/_about.py').read()); print(__version__)"

# =============================================================================
# Documentation
Expand Down Expand Up @@ -316,7 +320,7 @@ endif
@echo "==> [4/5] Setting up docs environment" && \
$(call use_env,VENV_DOCS,--with-docs) && \
echo "==> [5/5] Building documentation" && \
cd $(DOCS_DIR) && uv run --active sphinx-build -E -b html src build/html
cd $(DOCS_DIR) && uv run --no-sync --active sphinx-build -E -b html src build/html
ifndef _DOCS_ALL
@echo ""
@echo "════════════════════════════════════════════════════════════════════"
Expand Down
5 changes: 5 additions & 0 deletions agents/llm-first-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-s
- [3. Surgical Changes](#3-surgical-changes)
- [4. Goal-Driven Execution](#4-goal-driven-execution)
- [Success Indicators](#success-indicators)
- [References](#references)

## 1. Think Before Coding

Expand Down Expand Up @@ -78,3 +79,7 @@ These principles are working when:
- Diffs contain fewer unrelated changes.
- Fewer rewrites stem from overcomplication.
- Clarifying questions arrive before mistakes, not after.

## References

The principles above are adapted from Andrej Karpathy's coding guidance, via the [andrej-karpathy-skills](https://github.com/multica-ai/andrej-karpathy-skills) project (MIT License).
4 changes: 4 additions & 0 deletions ci/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# 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
4 changes: 4 additions & 0 deletions ci/nox/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# 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
109 changes: 109 additions & 0 deletions ci/nox/merge_pytest_results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env python3

# 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

"""Merge multiple pytest JUnit XML result files into a single file.

This script is used in CI to combine session-specific pytest results
(e.g., pytest-results-3.10.xml, pytest-results-3.11.xml) into a single
pytest-results.xml file.

Usage:
python merge_pytest_results.py [--input-dir DIR] [--output FILE] [--pattern PATTERN]

Examples:
python merge_pytest_results.py --input-dir results --output combined.xml
"""

import argparse
import logging
import sys
from pathlib import Path

from junitparser import JUnitXml

# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s: %(message)s",
)


def merge_junit_xml_files(
input_dir: str = "test-results",
output_file: str = "test-results/pytest-results.xml",
pattern: str = "pytest-results-*.xml",
) -> bool:
"""Merge multiple JUnit XML files into a single file.

Args:
input_dir: Directory containing the XML files to merge
output_file: Path to the output merged XML file
pattern: Glob pattern for matching input files

Returns:
True if merge was successful, False otherwise
"""
input_path = Path(input_dir)
xml_files = list(input_path.glob(pattern))

if not xml_files:
logging.info(f"No files matching '{pattern}' found in {input_dir}, nothing to merge")
return True

logging.info(f"Found {len(xml_files)} XML files to merge:")
for f in sorted(xml_files):
logging.info(f" - {f}")

merged = JUnitXml()
for xml_file in sorted(xml_files):
try:
merged += JUnitXml.fromfile(str(xml_file))
except Exception as e:
logging.error(f"Error parsing {xml_file}: {e}")
return False

output_path = Path(output_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
merged.write(str(output_path))

logging.info(f"Successfully merged into {output_file}")
return True


def main() -> int:
parser = argparse.ArgumentParser(
description="Merge multiple pytest JUnit XML result files into a single file."
)
parser.add_argument(
"--input-dir",
default="test-results",
help="Directory containing XML files to merge (default: test-results)",
)
parser.add_argument(
"--output",
default="test-results/pytest-results.xml",
help="Output file path (default: test-results/pytest-results.xml)",
)
parser.add_argument(
"--pattern",
default="pytest-results-*.xml",
help="Glob pattern for input files (default: pytest-results-*.xml)",
)

args = parser.parse_args()

success = merge_junit_xml_files(
input_dir=args.input_dir,
output_file=args.output,
pattern=args.pattern,
)

return not success


if __name__ == "__main__":
sys.exit(main())
Loading