Python Makefile alternative
Project description
pymake
A Python Makefile alternative with dependency tracking and parallel execution.
Installation
pip install -e .
Quick Start
Create a Makefile.py in your project:
from pymake import sh, task
@task(outputs=["build/app"])
def build():
sh("gcc -o build/app src/*.c")
@task(inputs=["build/app"])
def test():
sh("./build/app --test")
@task()
def clean():
sh("rm -rf build")
Run tasks:
pymake build # Run the build task
pymake test # Run test (builds first if needed)
pymake -B build # Force rebuild
pymake -p check # Run in parallel
Task Definition
Using the @task decorator
@task(inputs=["src/main.c"], outputs=["build/main.o"])
def compile():
"""Compile main.c to object file."""
sh("gcc -c src/main.c -o build/main.o")
Touch files
Use touch for tasks that don't produce output files but should track execution:
@task(touch="build/.lint-done")
def lint():
"""Run linter."""
sh("ruff check src/")
The touch file is created after the task runs and acts as an output for dependency tracking.
Dynamic registration
from pathlib import Path
from pymake import task
for src in Path("src").glob("*.c"):
obj = Path("build") / (src.stem + ".o")
def run(s=src, o=obj):
sh(f"gcc -c {s} -o {o}")
task.register(
run,
name=f"cc:{src}",
inputs=[src],
outputs=[obj],
)
Note: Use default arguments (s=src, o=obj) to capture loop variables. Without this, all tasks would reference the final loop values due to Python's closure semantics.
Default task
Set a default task to run when pymake is invoked without arguments:
task.default("check")
Meta tasks
Use task functions as inputs to create aggregate tasks:
@task()
def lint():
sh("ruff check src/")
@task()
def test():
sh("pytest")
@task(inputs=[lint, test])
def all():
pass
Dependency tasks run in order, each following normal run rules.
Execution Semantics
A task runs if any of these conditions are true (checked in order):
- Force flag:
-Bor--forcewas specified - Phony target: Task has no outputs (and no
touchfile) - Missing output: Any output file does not exist
- Stale output: Any input file is newer than the oldest output file
A task is skipped if:
- All outputs exist AND no inputs are defined (nothing to compare)
- All outputs exist AND all inputs are older than the oldest output
run_ifcallback returnsFalse(checked after file conditions)
Output files
Outputs can be specified via outputs or touch:
@task(outputs=["build/app"]) # Explicit output file
@task(touch="build/.done") # Touch file (auto-created after task runs)
@task() # Phony - always runs
The touch file is automatically created after successful execution and counts as an output.
Timestamp comparison
When comparing timestamps:
- pymake uses the oldest output file's mtime
- If any input is newer than this, the task runs
Input/Output validation
pymake enforces strict validation of input and output files:
-
Before execution: Each input file must either exist OR have a task that produces it. If neither is true, an error is raised immediately.
-
At task execution: All input files must exist when a task runs. If a producing task failed to create its outputs, dependent tasks will error.
-
After task execution: All declared output files must exist after the task completes (excluding
touchfiles, which are created automatically by pymake).
Custom Conditions
Use run_if for additional conditions after dependency checks:
def should_deploy():
return os.environ.get("DEPLOY") == "1"
@task(run_if=should_deploy)
def deploy():
sh("./deploy.sh")
Use run_if_not for the inverse (skip if condition is true):
def is_ci():
return os.environ.get("CI") == "1"
@task(run_if_not=is_ci)
def local_only():
"""Only runs locally, skipped in CI."""
sh("./local-setup.sh")
CLI Reference
pymake [options] [command] [targets...]
Commands:
list [--all] List tasks with docstrings (--all includes dynamic tasks)
graph <target> Output DOT graph of dependencies
which <output> Show reverse dependency tree for an output file
run <targets> Run specified targets
help Show help
Options:
-f, --file FILE Makefile path (default: Makefile.py)
-p, --parallel Enable parallel execution
-j, --jobs N Number of parallel workers
-B, --force Force rerun all tasks
-q, --quiet Suppress output
Shorthand:
pymake build Same as: pymake run build
pymake build test Same as: pymake run build test
Shell Utility
The sh() function runs shell commands:
from pymake import sh
sh("echo hello") # Output to terminal
output = sh("cat file", capture=True) # Capture output
sh("might-fail", check=False) # Don't raise on error
Dependency Graph
Generate a DOT graph for visualization:
pymake graph build | dot -Tpng > deps.png
Reverse Dependency Lookup
Find which task produces an output file and trace its dependencies:
$ pymake which final.txt
final.txt
└── finalize
│ ← derived.txt
│ → final.txt
└── process
│ ← base.txt
│ → derived.txt
└── generate_base
→ base.txt
The tree shows inputs (←) and outputs (→) for each task.
Error Handling
- Cyclic dependencies are detected and reported
- Duplicate output files across tasks raise an error
- Task failures stop execution and report the error
- Missing input files (not produced by any task) raise
UnproducibleInputError - Input files that don't exist at execution time raise
MissingInputError - Output files not created by a task raise
MissingOutputError
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hayeah_pymake-0.1.0.tar.gz.
File metadata
- Download URL: hayeah_pymake-0.1.0.tar.gz
- Upload date:
- Size: 15.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f75c08efd31859db6cd958f18c62a0d758c7779b5a0543d484124194c05ac565
|
|
| MD5 |
0572967e18b5c934a8f1f3734ebb45f2
|
|
| BLAKE2b-256 |
53498d507983c21240f631b63cfe5ecde82f53280047a632158822b836aeace3
|
File details
Details for the file hayeah_pymake-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hayeah_pymake-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc9715de8915e7e765bebf4f0d07894865197fc4ab0a1db6668c7a76b770129b
|
|
| MD5 |
6d0a6d42b1a6ed8af4eb8c0eeeaee8af
|
|
| BLAKE2b-256 |
57a13370aa97014e20ee1b61adad55c5b0deb68f492ad0fc5d859cd56d250834
|