Unified developer CLI for Python packages with C and C++ bindings.
Project description
foga
foga is a Python package and CLI for developers maintaining Python packages
with native C or C++ bindings. It replaces ad-hoc repository scripts with a
single YAML configuration file that drives build, test, deploy, inspect, and
cleanup workflows.
What foga does
foga gives a project one entrypoint for the common tasks that usually end up
split across Makefiles, shell scripts, CI snippets, and README notes:
- build Python packages and native artifacts from one config file
- run Python and native test workflows through one CLI
- switch environment-specific settings with named profiles
- inspect the resolved config before running anything
- keep escape hatches explicit through structured hooks
The repository includes a full example in
examples/qupled/foga.yml.
Installation
Install foga as a normal user with:
pip install foga
Quick Start
- Add a root-level
foga.ymlfile to your project. - Start with a minimal configuration.
- Validate it.
- Inspect the resolved config.
- Run build or test workflows.
Minimal example:
project:
name: demo
build:
python:
backend: python-build
test:
runners:
unit:
backend: pytest
path: tests
Typical commands:
These examples assume you are running foga from a project root that contains
foga.yml:
foga validate # Check that foga.yml is well-formed
foga inspect # Print the resolved configuration
foga build --dry-run # Show planned build commands without executing
foga test --dry-run # Show planned test commands without executing
foga deploy --target pypi --dry-run # Preview the deploy command
End-To-End Workflow
The usual workflow for adopting foga in a repository is:
- Create
foga.ymlwith your project name and at least one build or test workflow. - Run
foga validateuntil the configuration passes. - Run
foga inspectto check the merged effective config. - Use
foga build --dry-run,foga test --dry-run, orfoga deploy --dry-runto inspect generated commands before execution. - Run the real command once the plan looks right.
- Add profiles only after the base config is working.
That sequence keeps adoption incremental. You do not need to encode every project script on day one.
Configuration Layout
foga expects a root-level YAML mapping. The main top-level sections are:
project: required project metadatabuild: optional build workflowstest: optional test workflowsdeploy: optional deployment workflowsclean: optional cleanup targetsprofiles: optional named overrides applied on top of the base config
Example shape:
project:
name: demo
build:
default: all
native:
backend: cmake
source_dir: cpp
build_dir: build
python:
backend: python-build
test:
default: python
runners:
unit:
backend: pytest
path: tests
deploy:
targets:
pypi:
backend: twine
artifacts: ["dist/*"]
clean:
paths: ["build", "dist"]
profiles:
release:
build:
python:
args: ["--wheel"]
Project
project.name is required and identifies the configured project in validation
output.
Build
build defines up to two workflow kinds:
build.nativefor native build toolingbuild.pythonfor Python package builds
build.default may be native, python, or all.
When build.default is omitted, foga build runs all configured build kinds
for backward compatibility.
build itself is optional, but foga build only does useful work when at
least one build workflow is configured.
Test
test.runners is a mapping keyed by runner name. Each runner chooses a backend
such as pytest, tox, or ctest.
test.default may be native, python, or all.
test is optional, but test.runners is the important nested section when you
want foga test to run anything.
Deploy
deploy.targets is a mapping keyed by target name. Each target currently uses
the twine backend to upload matched artifacts.
deploy is optional. Configure it only if you want foga deploy.
Clean
clean.paths is a simple list of repository-relative paths that foga clean
removes.
clean is optional.
Supported Backends
Build backends
cmake
build.native.backend: cmake generates a configure step and one or more
cmake --build steps. Its fields mean:
source_dir: source tree passed tocmake -Sbuild_dir: build tree passed tocmake -Band reused forcmake --buildgenerator: optional generator name such asNinjaconfigure_args: extra flags appended to the configure stepbuild_args: extra flags appended to the build steptargets: default native targets to build when the CLI does not override themenv: environment variables added to the generated commandshooks: pre/post commands run around the native workflow
python-build
build.python.backend: python-build runs python3 -m build with optional
extra args, plus optional env and hooks.
foga intentionally does not allow overriding the full build command for this
backend. Use args for extra flags.
Its fields mean:
args: extra flags appended topython3 -m buildenv: environment variables added to the build commandhooks: pre/post commands run around the Python package build
Test backends
pytest
This backend runs pytest. Its fields mean:
path: test path passed topytest; this is required for thepytestbackendmarker: optionalpytest -mselectorargs: extra flags appended after the base pytest commandenv: environment variables added to the runner commandhooks: pre/post commands run around the runner
tox
This backend runs tox -e <env>. Its fields mean:
tox_env: environment name passed totox -e; this is required for thetoxbackendargs: extra flags appended aftertox -e <env>env: environment variables added to the runner commandhooks: pre/post commands run around the runner
ctest
This backend can configure and build native tests before running ctest. Its
fields mean:
build_dir: build tree used byctest --test-dir; this is requiredsource_dir: optional source tree; when present,fogaalso runs a CMake configure step before testinggenerator: optional generator name for the configure stepconfigure_args: extra flags appended to the configure stepbuild_args: extra flags appended to the native build steptarget: optional build target compiled before tests runargs: extra flags appended to the finalctestcommandenv: environment variables added to generated commandshooks: pre/post commands run around the native test workflow
Deploy backends
twine
This backend runs twine upload. Its fields mean:
artifacts: glob patterns resolved relative to the project root; this is required and must match built package filesrepository: optional Twine repository name passed as--repositoryrepository_url: optional explicit upload URL passed as--repository-urlargs: extra flags appended before artifact pathsenv: environment variables added to the upload commandhooks: pre/post commands run around the upload step
Profiles
Profiles let one repository express environment-specific differences without
copying the entire config. Apply them with --profile <name>.
Example:
profiles:
mpi:
build:
native:
configure_args:
- -DBUILD_NATIVE_TESTS=OFF
- -DUSE_MPI=ON
python:
env:
USE_MPI: "ON"
Use profiles for:
- CI versus local development
- MPI versus non-MPI builds
- platform-specific environment variables
- release-only deployment settings
Profile merge rules are intentionally conservative:
- profile overrides may replace values and extend nested mappings
- they must preserve the container type of existing paths
- they cannot change the backend identifier of an already configured workflow
Hooks And Escape Hatches
Hooks are the supported escape hatch when a workflow needs a small amount of custom orchestration around a built-in backend command.
Hook shape:
test:
runners:
integration:
backend: pytest
path: tests
hooks:
pre:
- ["python3", "tools/prepare_integration.py"]
post:
- ["python3", "tools/cleanup_integration.py"]
Supported behavior:
- only
hooks.preandhooks.postare supported - each hook entry must be a non-empty command array
- hooks run directly without shell parsing
- hooks execute around the generated backend command
Intentionally unsupported:
- shell strings such as
"make build && make test" - per-hook mappings such as
cwd,shell,argv, or inlineenv - turning the config file into a generic task runner
If logic is complex, keep it in a project script and call that script from a hook.
Command Guide
Validate
Use foga validate to catch malformed config early:
foga validate # Validate the default ./foga.yml
foga --config path/to/foga.yml validate # Validate a specific config file
This is the first command to run after editing the configuration.
Build
Use foga build to run configured build workflows:
foga build --dry-run # Preview all configured build workflows
foga build python --dry-run # Preview only the Python package build
foga build native --target native_tests --dry-run # Preview one native target explicitly
foga build all --profile mpi --dry-run # Preview builds with the mpi profile applied
foga build --dry-run # Print the planned build commands
Test
Use foga test to run one or more configured test runners:
foga test --dry-run # Preview all configured test workflows
foga test python --runner unit --dry-run # Preview only the Python runner named "unit"
foga test native --dry-run # Preview only native test workflows
foga test --profile mpi --dry-run # Preview test commands with the mpi profile
Deploy
Use foga deploy to run deployment targets:
foga deploy --target pypi --dry-run # Preview the pypi upload command
Clean
Use foga clean to remove configured generated paths:
foga clean # Remove the paths listed under clean.paths
Inspect
Use foga inspect to print the resolved configuration without executing
commands:
foga inspect # Print the full resolved config
foga inspect --profile mpi # Inspect after applying mpi
foga inspect build native --target native_tests # Inspect native build selection
foga inspect test python --runner unit # Inspect the selected test runner
foga inspect deploy --target pypi # Inspect one deploy target
foga inspect --full build native # Show the full document for build
Top-level foga inspect prints the full resolved config. Command-specific
inspection prints a concise summary plus the relevant config fragment unless
--full is set.
Dry-Run Usage
Dry-run mode is the safest way to adopt foga in an existing repository.
Available dry-run commands:
foga build --dry-runfoga test --dry-runfoga deploy --dry-run
Dry-run output shows the planned commands without executing them. Use it to verify:
- the selected profile
- target or runner filtering
- generated backend arguments
- hook ordering
- working assumptions before changing CI or repository scripts
Override Precedence
foga resolves configuration in this order:
- Base
foga.yml - Selected profile overrides from
profiles.<name> - CLI overrides for the active command
CLI overrides are execution-scoped. They do not rewrite the config file.
Examples:
foga build pythonchanges the build selection for one invocationfoga test nativechanges the test selection for one invocationfoga build --target native_testsoverrides configured native targetsfoga test --runner unitnarrows the selected runnersfoga deploy --target pypinarrows the selected deploy targets
Migration From Repo-Specific Scripts
Repositories adopting foga usually already have shell scripts, Make targets,
or CI snippets for build and test commands. The migration goal is not to delete
everything immediately. Start by moving the stable workflow definition into
foga.yml.
Suggested migration path:
- Inventory the commands your repository already uses for build, test, deploy, and cleanup.
- Map each stable workflow to a built-in backend first:
python -m build->python-build,pytest->pytest,tox -e <env>->tox,cmakeorctest-> native backends. - Keep repo-specific scripts only for logic that is genuinely project-specific.
- Wrap small prep or cleanup steps with hooks instead of copying full shell scripts into YAML.
- Replace CI shell fragments with
fogacommands once dry-run output and local execution are stable. - Remove obsolete scripts only after the
fogaworkflow is trusted.
Concrete before-and-after examples:
scripts/build_wheel.shthat only runspython -m buildusually becomesbuild.python.backend: python-buildscripts/test_unit.shthat only wrapspytest tests -m unit -vusually becomes a namedpytestrunner withpath,marker, andargs- platform-specific environment setup usually belongs in a profile
- a short docs-copy step around integration tests usually belongs in hooks
Good candidates to keep outside foga:
- long project bootstrap flows
- commands that provision external infrastructure
- heavy orchestration that is better expressed in Python or shell than YAML
Example Config
examples/qupled/foga.yml demonstrates:
- both native and Python build workflows
- multiple named test runners
- pre/post hooks for integration tests
- MPI and platform-specific profiles
Use it as a reference when authoring a new config, but keep your own config as small as possible at first.
Repository Development
If you are developing this repository itself, install it in editable mode with the development dependencies:
pip install -e .[dev]
For day-to-day validation of changes in this repository, the standard checks are:
ruff check .
pytest
python -m build
Devcontainer
The repository includes a devcontainer in
.devcontainer/devcontainer.json. When
opened in a compatible environment, it installs:
- Python 3.11
fogain editable mode with thedevextra, includingruff- common native-tooling dependencies used by package workflows:
build-essential,clang,cmake,ninja-build,pkg-config, andgdb - GitHub CLI (
gh) for authenticated GitHub API and repository operations - Codex via
npm
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 foga-0.1.0.tar.gz.
File metadata
- Download URL: foga-0.1.0.tar.gz
- Upload date:
- Size: 45.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dcbfd6dbf175989546d8667bda2e59f8dcbc067dcaa33c15b14a0ec7f7f74e9e
|
|
| MD5 |
b0deb1c964ed2d611db046ce74fbc999
|
|
| BLAKE2b-256 |
09cacf70b92912f9cede33b0cef916915a86da6db59661c03d67ef6746a029ae
|
Provenance
The following attestation bundles were made for foga-0.1.0.tar.gz:
Publisher:
publish.yml on fedluc/foga
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
foga-0.1.0.tar.gz -
Subject digest:
dcbfd6dbf175989546d8667bda2e59f8dcbc067dcaa33c15b14a0ec7f7f74e9e - Sigstore transparency entry: 1285221674
- Sigstore integration time:
-
Permalink:
fedluc/foga@427d09bf8fa3cb868e74721b41b52faf73d1834b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/fedluc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@427d09bf8fa3cb868e74721b41b52faf73d1834b -
Trigger Event:
push
-
Statement type:
File details
Details for the file foga-0.1.0-py3-none-any.whl.
File metadata
- Download URL: foga-0.1.0-py3-none-any.whl
- Upload date:
- Size: 38.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed72864c7620b36e79aeff6f9e20bd33441d1a64373531a30cdffd10e0573942
|
|
| MD5 |
1fe536b09cd43d395f4e4aee49770ac1
|
|
| BLAKE2b-256 |
035ad4317b59f08fac503af634ef61a4e60f6376d89e662a4fa9f87d5851a656
|
Provenance
The following attestation bundles were made for foga-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on fedluc/foga
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
foga-0.1.0-py3-none-any.whl -
Subject digest:
ed72864c7620b36e79aeff6f9e20bd33441d1a64373531a30cdffd10e0573942 - Sigstore transparency entry: 1285221788
- Sigstore integration time:
-
Permalink:
fedluc/foga@427d09bf8fa3cb868e74721b41b52faf73d1834b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/fedluc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@427d09bf8fa3cb868e74721b41b52faf73d1834b -
Trigger Event:
push
-
Statement type: