Skip to main content

Python Makefile (pmake)

Using Makefiles for project command orchestration is becoming more common.

However often writing logic in bash is a pain - pmake allows writing everything in Python using the powerful amoffat/sh library.

Note: Python Makefile (pmake) is not for building CLIs, just a simple way to run orchestration within your project.

Overview

# Create a Makefile.py in your project directory (see guidance with pmake when no file exists)

# Run the default command from your Makefile.py
pmake

# Run specific command directly
pmake build_images
pmake status
pmake build_and_push

# Pass parameters and override environment variables
pmake build_images IMAGE=myapp VERSION=1.0.0

Example:

Content in Makefile.py

from pmake import sh, _, dep
from pmake import docker, git, echo  # Direct command imports

# Read from env
DOCKER_REPO = _('DOCKER_REPO')
IMAGE = _('IMAGE')
VERSION = _('VERSION', "0.0.1")  # Default value

def build_images():
    # Use sh object - automatically inherits environment including venv
    sh.docker("build", "-f", "./docker/Dockerfile", ".",
              "-t", f"{DOCKER_REPO}/{IMAGE}:{VERSION}")

def push_images():
    # Use direct command imports - environment variables from _() automatically available
    docker("push", f"{DOCKER_REPO}/{IMAGE}:{VERSION}")

def status():
    # All commands automatically use your current shell environment
    git("status")
    echo(f"Build completed for {IMAGE} v{VERSION}!")

@dep(build_images, push_images)
def build_and_push():
    pass

Run commands

# Runs the default command (first alphabetically: build_and_push in this case)
pmake

# Run specific commands directly
pmake build_images
pmake push_images
pmake status

# Runs build_and_push (with dependencies: build_images, push_images)
pmake build_and_push

# Run with parameter overrides
pmake build_and_push IMAGE="myimage" VERSION="2.0.0"

# See all available commands with descriptions and dependencies
pmake --help

Getting Started

Creating Your First Makefile.py

When you run pmake in a directory without a Makefile.py, it will show helpful guidance. Here's a simple starter example:

from pmake import sh, _, dep
from pmake import echo, python, pip

def hello():
    '''Say hello - this will be your default command'''
    echo('Hello from pmake!')

def test():
    '''Run tests'''
    python('-m', 'pytest')

@dep(test)
def deploy():
    '''Deploy after running tests'''
    echo('Deploying application...')

Then run:

  • pmake - Runs default command (hello in this case)
  • pmake test - Runs test command
  • pmake deploy - Runs test, then deploy (due to @dep dependency)
  • pmake --help - Shows all available commands with descriptions and dependencies

Features

Universal Command Imports

Import any shell command directly from pmake:

from pmake import docker, git, echo, ls, grep, curl, wget, find
# Equivalent to: from sh import docker, git, echo, ls, grep, curl, wget, find

# Use them directly
docker("build", "-t", "myapp", ".")
git("commit", "-m", "Update")
echo("Hello World")

Powerful Shell Execution with Automatic Environment Inheritance

pmake uses the amoffat/sh library under the hood with enhanced environment management:

  • Automatic virtual environment inheritance: Commands use your current venv
  • Environment variable tracking: Variables accessed via _() are automatically available to shell commands
  • Parameter override inheritance: CLI PARAM=value overrides are passed to all shell commands
  • Better error handling and superior command composition
  • Rich command objects with piping support

No more _env=os.environ needed! All pmake shell commands automatically include:

  • Your current shell environment (including PATH, VIRTUAL_ENV, etc.)
  • Any environment variables accessed through _() calls
  • Any parameter overrides from the command line

Automatic Environment Inheritance

All shell commands automatically inherit your environment:

from pmake import sh, python, pip, _

# Environment variables accessed via _() are tracked
VENV_PATH = _('VIRTUAL_ENV')
PROJECT_NAME = _('PROJECT_NAME', 'myproject')

def setup_venv():
    # python command automatically uses current virtual environment
    python("-m", "pip", "install", "-e", ".")

    # No need for _env=os.environ - it's automatic!
    pip("install", "pytest", "black", "mypy")

def test():
    # All environment variables from _() calls are available
    python("-c", f"print('Testing {PROJECT_NAME}')")

Both Syntaxes Supported

from pmake import sh, docker

# Option 1: Direct imports (automatically inherit environment)
docker("build", "-t", "myapp", ".")

# Option 2: Through sh object (automatically inherit environment)
sh.docker("build", "-t", "myapp", ".")

# Complex shell operations (with full environment)
sh.bash("-c", "docker build . | grep 'Successfully built'")

Equivalent makefile

IMAGE ?= $(IMAGE)
DOCKER_REPO ?= $(DOCKER_REPO)
VERSION ?= $(VERSION)

build_images:
	docker build -f ./docker/Dockerfile . \
		-t $(DOCKER_REPO)/$(IMAGE):$(VERSION)

push_images:
	docker push $(DOCKER_REPO)/$(IMAGE):$(VERSION)

build_and_push: build_images, push_images

Migration from bash()

The old bash() function has been removed in favor of direct command usage:

# OLD (removed)
bash("echo hello")
bash("docker build -t myapp .")

# NEW - Direct imports
from pmake import echo, docker
echo("hello")
docker("build", "-t", "myapp", ".")

# NEW - sh object
from pmake import sh
sh.echo("hello")
sh.docker("build", "-t", "myapp", ".")

Metadata

Release files for python-pmake 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for python-pmake 0.1.0
File Size Uploaded Provenance
python_pmake-0.1.0.tar.gz 19.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for python-pmake 0.1.0
File Interpreter ABI Platform
python_pmake-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 29.5 kB

Release files / python_pmake-0.1.0.tar.gz

Download URL python_pmake-0.1.0.tar.gz
Size 19.9 kB
Tags Source
SHA-256 checksum
How to use checksums
abc6b7f971d4e57a9e15299deee65c084d99365201955abfaf5937be2cb90ab7
BLAKE2b-256 checksum
How to use checksums
8cb386e981495467560ad732900515929f1a15d4ce10bc2a6c526606ec404118
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.13

Release files / python_pmake-0.1.0-py3-none-any.whl

Download URL python_pmake-0.1.0-py3-none-any.whl
Size 9.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0837fb8ba5f7aae388976fb360de22780693649ef350979a2d97d67e94f0f610
BLAKE2b-256 checksum
How to use checksums
920e21d94853cd85b43294a9f825bde6946010c4006fa1c9ab430c3f575d23ae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.13

Security for python-pmake 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page