Skip to main content

No project description provided

Project description

pymaketool

pymaketool is an elegant and simple tool to build and manage large C/C++ projects and libraries. The main purpose is to simplify the build process by using Python to find and organize source files.

๐Ÿ“‚ my_project/

  • ๐Ÿ“„ Makefile โ† root entry point
  • ๐Ÿ“„ .env โ† environment variables (not committed)
  • ๐Ÿ“‚ pymake/ โ† build config folder
    • ๐Ÿ“„ Makefile.py โœ๏ธ YOU write this ยท toolchain ยท flags ยท targets
    • ๐Ÿ“„ makefile.mk โ† generated
    • ๐Ÿ“„ vars.mk โ† generated
    • ๐Ÿ“„ srcs.mk โ† generated
    • ๐Ÿ“„ targets.mk โ† generated
  • ๐Ÿ“‚ app/ โ† source module
    • ๐Ÿ“‚ inc/
      • ๐Ÿ“„ main.h
    • ๐Ÿ“‚ src/
      • ๐Ÿ“„ main.c
    • ๐Ÿ mk.py โœ๏ธ YOU write this ยท recommended
      from pm import mk
      
      mk(srcs=["src/main.c"], incs=["inc"])
      
      ## Or just:
      from pm import mk
      
      mk()
      
      ## Or nothing at all:
      
  • ๐Ÿ“‚ utils/ โ† another source module
    • ๐Ÿ“„ utils.c
    • ๐Ÿ“„ utils.h
    • ๐Ÿ utils_mk.py โœ๏ธ also supported
  • ๐Ÿ“‚ build/ โ† generated output
    • โš™๏ธ app โ† final binary
    • ๐Ÿ“‚ obj/
      • ๐Ÿ“„ app/src/main.o
      • ๐Ÿ“„ utils/utils.o

Quick Start

Install required packages:

Ubuntu

$ sudo apt-get install -y gcc make python3 python3-pip git time zip

Fedora

$ sudo dnf install python3 python3-pip time zip git gcc

Arch Linux

$ sudo pacman -S gcc make python python-pip time zip git

Install pymaketool:

$ pip3 install pymaketool

Create and build a new C project:

$ pynewproject CLinuxGCC
  (author) Your name: Ericson
  (project_name) Your project name: hello

$ cd hello
hello$ make
hello$ ./build/hello

Quick Start with Poetry

If you prefer Poetry for dependency management:

$ curl -sSL https://install.python-poetry.org | python3 -

$ pynewproject CLinuxGCC
  (project_name) Your project name: hello

$ cd hello
hello$ poetry init --name hello --dependency pymaketool
hello$ poetry install
hello$ poetry run make
hello$ ./build/hello

Quick start in Docker

$ docker pull ericsonjoseph/pymaketool
$ docker run -it ericsonjoseph/pymaketool
ubuntu@$ pynewproject CLinuxGCC

Module Files (mk.py, *_mk.py, *.mk.py)

pymaketool discovers module files recursively. The shortest and recommended name is mk.py, but app_mk.py and app.mk.py still work.

Each module file describes one folder of C/C++ code.

Recommended DX: from pm import mk

# app/mk.py
from pm import mk

mk()

That is enough for most modules. mk() captures the current file path and auto-discovers sources and includes in the same directory. If you want zero lines, an empty mk.py also works.

Common cases

Explicit files:

from pm import mk

mk(srcs=["src/main.c", "src/util.c"], incs=["inc"])

Exclude files from auto-discovery:

from pm import mk, skip

mk(srcs=skip("test*", "mock_*"), incs=[".", "include"])

C++ module:

from pm import mk

mk(lang="cpp")

When to use the class API

Use the class-based API only when you need custom module behavior, static libraries, or full control over discovery.

from pymakelib import module

@module.ModuleClass
class App(module.BasicCModule):
    pass

Ignoring Modules During Discovery

pymaketool automatically reads .gitignore by default and applies gitignore-style pattern matching to skip module files during discovery. You can also use .moduleignore for pymaketool-specific ignore patterns.

Quick start

Create a .moduleignore file in pymake/ (or project root as fallback):

# Ignore test modules
tests/
*_test_mk.py

# Ignore vendor code
vendor/
third_party/

# Ignore specific modules
experimental/prototype_mk.py

Pattern syntax

.moduleignore and .gitignore use gitignore-style patterns:

Pattern Matches
test_*.py Any file starting with test_
build/ Entire build/ directory and its contents
**/temp_* temp_* files at any depth
*.tmp Any file ending with .tmp
!important_mk.py Negation: exclude important_mk.py from ignore

Comments (#) and blank lines are ignored.

Source precedence

Ignore patterns are merged from multiple sources in this order:

  1. .gitignore (project root) โ€” read by default
  2. .moduleignore (pymake/.moduleignore preferred, fallback to root .moduleignore)
  3. Makefile.py โ€” additional patterns via ignore_list

Later sources can override earlier ones using negation (!pattern).

Control via Makefile.py

Disable .gitignore or add extra patterns:

from pymakelib import ProjectConfig, Makeclass

@Makeclass
class Build(ProjectConfig):
    use_gitignore = False               # disable .gitignore reading
    ignore_list   = ['vendor/', 'tmp/'] # additional patterns
    ...

Or override the getIgnoreConfig() method for dynamic control:

@Makeclass
class Build(ProjectConfig):
    ...
    
    def getIgnoreConfig(self):
        return {
            'use_gitignore': True,
            'ignore_list': ['tests/', 'experimental/']
        }

File location

  • Preferred: pymake/.moduleignore (alongside Makefile.py)
  • Fallback: .moduleignore in project root (for legacy projects)

Why .gitignore by default? Most projects already ignore build artifacts, vendor code, and tests in .gitignore. Reading it by default reduces duplication and follows the principle of least surprise.


Project Configuration (Makefile.py)

Makefile.py is the project entry point. It defines the toolchain, compiler flags, and build targets.

New API โ€” ProjectConfig (recommended)

# pymake/Makefile.py
from pymakelib import ProjectConfig, Makeclass, CompilerOpts, MKVARS, Target
from pymakelib.toolchain import get_gcc_linux

@Makeclass
class Build(ProjectConfig):
    name         = 'myapp'
    output_dir   = 'build/obj/'
    compiler_set = get_gcc_linux()   # or get_gcc_arm_none_eabi('/opt/arm/bin/')

    def compiler_opts(self, opts: CompilerOpts) -> CompilerOpts:
        opts.optimize     = ['-O2']
        opts.debugging    = ['-g3']
        opts.warnings     = ['-Wall']
        opts.standard     = ['-std=c11']
        opts.preprocessor = ['-MP', '-MMD']
        opts.macros       = {'VERSION': '"1.0"'}
        return opts

    def targets(self):
        return {
            'TARGET': Target(
                file   = f'build/{self.name}',
                script = [MKVARS.LD, '-o', '$@', MKVARS.OBJECTS, MKVARS.LDFLAGS],
                logkey = 'OUT',
            ),
        }

Multi-output builds (ELF โ†’ HEX โ†’ BIN) โ€” target dict order defines the dependency chain:

    def targets(self):
        return {
            'TARGET':     Target(file='build/app.elf',
                                 script=[MKVARS.LD, '-o', '$@', MKVARS.OBJECTS, MKVARS.LDFLAGS],
                                 logkey='LINK'),
            'TARGET_HEX': Target(file='build/app.hex',
                                 script=['objcopy', '-O', 'ihex', MKVARS.TARGET, '$@'],
                                 logkey='HEX'),
            'TARGET_BIN': Target(file='build/app.bin',
                                 script=[MKVARS.OBJCOPY, '-O', 'binary', MKVARS.TARGET, '$@'],
                                 logkey='BIN'),
        }

Phony targets (flash, format, testโ€ฆ):

    def getPhonyTargets(self):
        return {
            'flash': {
                'deps':   ['all'],
                'logkey': 'FLASH',
                'script': 'openocd -f board/board.cfg -c "program build/app.elf verify reset exit"',
            },
        }

IDE addons โ€” declare as a list attribute instead of a module-level call:

from pymakelib.eclipse_addon import EclipseAddon
from pymakelib.vscode_addon import VSCodeAddon

@Makeclass
class Build(ProjectConfig):
    addons       = [EclipseAddon, VSCodeAddon]
    compiler_set = get_gcc_linux()
    ...

Linker options:

    def linker_opts(self, opts: LinkerOpts) -> LinkerOpts:
        opts.script  = ['-T', 'link.ld']
        opts.machine = ['-mthumb', '-mcpu=cortex-m4']
        opts.libs    = ['-lm', '-lc']
        return opts

Environment variables (.env support)

# pymake/Makefile.py
from pymakelib import ProjectConfig, Makeclass, resolve_env
from pymakelib.toolchain import get_gcc_arm_none_eabi

@Makeclass
class Build(ProjectConfig):
    env_file     = '.env'   # loaded automatically before any get* call
    compiler_set = get_gcc_arm_none_eabi(resolve_env('XC32_PATH', '/opt/xc32/bin/'))
    ...

.env file (not committed to version control):

XC32_PATH=/opt/microchip/xc32/v4.60/bin
PROGRAMMER=PK5

load_dotenv() and resolve_env() are also available for module-level use:

from pymakelib import load_dotenv, resolve_env

load_dotenv()
TOOLCHAIN = resolve_env('TOOLCHAIN_PATH', '/usr/bin/')

Toolchain presets

Function Description
get_gcc_linux() Host Linux GCC
get_gpp_linux() Host Linux G++ (C++)
get_gcc_arm_none_eabi() ARM bare-metal cross-compiler

All return a CompilerSet dataclass with IDE autocompletion for every tool path.


Typed Configuration Reference

All new configuration types live in pymakelib and provide IDE autocompletion.

CompilerOpts

Field Makefile variable Example
macros -D defines {'DEBUG': None, 'VER': '"2.0"'}
machine machine/arch flags ['-mthumb', '-mcpu=cortex-m4']
optimize optimisation ['-O2']
debugging debug info ['-g3']
preprocessor preprocessor ['-MP', '-MMD']
warnings warnings ['-Wall', '-Werror']
standard language std ['-std=c11']
general other flags ['--coverage']

Target

Target(
    file   = 'build/app.elf',           # output file path
    script = [MKVARS.LD, '-o', '$@',    # command tokens joined with spaces
               MKVARS.OBJECTS, MKVARS.LDFLAGS],
    logkey = 'LINK',                     # label shown in build output
)

script tokens are joined with a space into one Makefile recipe line. Use && for shell chaining: ['@mkdir -p $(dir $@) &&', MKVARS.LD, ...].


For install guide go to install-guide

For more documentation go to Read the Docs

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymaketool-3.0.0rc6.tar.gz (218.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pymaketool-3.0.0rc6-py3-none-any.whl (252.4 kB view details)

Uploaded Python 3

File details

Details for the file pymaketool-3.0.0rc6.tar.gz.

File metadata

  • Download URL: pymaketool-3.0.0rc6.tar.gz
  • Upload date:
  • Size: 218.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.0 CPython/3.14.4 Linux/6.17.0-1010-azure

File hashes

Hashes for pymaketool-3.0.0rc6.tar.gz
Algorithm Hash digest
SHA256 654e2878dd53b50366710e62ae642e20b6e4e2899d6995c4cb5975ba40e247bd
MD5 91636833deee1af85bc0ac12d99baa77
BLAKE2b-256 372921b8aa433d26be295aa2981d57d6696ca72ce8c8b9ee8a9918dd4c3b2ffa

See more details on using hashes here.

File details

Details for the file pymaketool-3.0.0rc6-py3-none-any.whl.

File metadata

  • Download URL: pymaketool-3.0.0rc6-py3-none-any.whl
  • Upload date:
  • Size: 252.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.0 CPython/3.14.4 Linux/6.17.0-1010-azure

File hashes

Hashes for pymaketool-3.0.0rc6-py3-none-any.whl
Algorithm Hash digest
SHA256 b01724feb2e671d019dd9e4107be3391801a8c41745d9e759c7975f41fe7bbda
MD5 a47d0ad51adff0207241508edb0d7ee2
BLAKE2b-256 c7104bfbca51074b1242eba246d1c6cbb9ae9ff7d40d6999aa93d3c49107c103

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page