Skip to main content

Trellis

Trellis configures a build from a tree of options written in a subset of the Linux Kconfig language. The user's choices are kept in a defconfig, and a SAT solver resolves them into one value for every option. When choices cannot all hold, Trellis names the smallest set of choices and rules in conflict and lists the changes that would remove it, instead of quietly turning an option off. It is a Python library first, with a command, a line prompter and a full-screen menu on top.

Trellis is the configuration system of PyrixOS, where lattice config runs it. It is published on its own so any project with a Kconfig-style option tree can use it.

Resolving is repeatable. The same tree and the same choices always give the same configuration, whichever solver backend is used and whichever model the solver finds on the way. The resolved configuration is written as config.pyb, a file that PyBonsai or plain Python can read.

Status

Stable since 1.0.0. Two solver backends give the same results:

Backend Solver Needs
builtin (default) A CDCL solver in pure Python Nothing
picosat PicoSAT, the solver the Linux kernel's ConfigFix uses, through ctypes libpicosat

PicoSAT is found through ctypes.util.find_library("picosat"), or at the path in the TRELLIS_PICOSAT environment variable. The tests compare both backends with a brute-force check on random option trees. The coverage floor is 80%.

Install

Trellis is published on PyPI as trellis-kconfig, since the name trellis is taken. The import name and the command are trellis. To use it in another project:

pip install trellis-kconfig

or add trellis-kconfig>=1.0,<2 to the project's dependencies.

To work on Trellis itself:

python -m pip install --upgrade "pip>=25.1"
pip install --group dev
pip install --config-settings editable_mode=strict -e .
pybootstrap check

Python 3.12 or later is required. The only runtime dependency is Pydantic 2. The dev dependency group installs pybootstrap and the tools its gates drive: ruff, pyright, and pytest with coverage. The PicoSAT tests are skipped when the library cannot be loaded.

Releases are made by pushing a tag vX.Y.Z that matches __version__ and has a section in CHANGELOG.md. The release workflow runs the gates, builds the package, publishes it to PyPI through trusted publishing with attestations, and creates the GitHub release.

An example

config MODULES
	bool "Loadable module support"
	modules
	default y

menu "Networking"

config NET
	bool "Networking support"
	default y

config WIFI
	tristate "Wireless LAN"
	depends on NET
	select CRYPTO
	help
	  Drivers for wireless network cards.

endmenu

config CRYPTO
	tristate "Cryptographic API"

With this tree in Kconfig:

$ trellis set WIFI=m
trellis: resolved 4 options in 0.3 ms
$ cat defconfig
CONFIG_WIFI=m
$ cat config.pyb
# Generated by trellis from the option tree "Configuration". Do not edit:
# change the defconfig and run trellis olddefconfig.

options = {
    "MODULES": True,
    "NET": True,
    "WIFI": "m",
    "CRYPTO": "m",
}
$ trellis explain CRYPTO
CRYPTO = m
  WIFI=m selects it (Kconfig:12)
  it cannot be n: you set WIFI=m; WIFI selects CRYPTO
$ trellis set NET=n
trellis: these choices and rules cannot all hold:
  you set NET=n
  you set WIFI=m
  WIFI depends on NET (Kconfig:12)
trellis: run trellis explain NAME VALUE to see the ways to allow a choice
$ trellis explain CRYPTO n
these choices and rules cannot all hold:
  you set WIFI=m
  WIFI selects CRYPTO (Kconfig:12)
1. drop WIFI=m
     WIFI: m -> n
     CRYPTO: m -> n

The defconfig holds only what the user chose. A failed set changes nothing on disk.

Use

from trellis import Configurator, ConflictError

configurator = Configurator.load("Kconfig", "defconfig")
configurator.set("WIFI", "m")
print(configurator.resolve().values)  # {'MODULES': 'y', 'NET': 'y', 'WIFI': 'm', 'CRYPTO': 'm'}

conflict = configurator.conflict("NET", "n")
if conflict is not None:
    print(conflict.describe())
    for fix in configurator.fixes("NET", "n"):
        print("drop", fix.dropped, "changes", fix.changes)

configurator.save("defconfig", "config.pyb")

Configurator.load reads the tree and the defconfig, if it exists; Configurator(tree) takes a tree already parsed with parse_file or parse_text. Both take backend="picosat" to choose the solver. Choices for options the tree does not have, or cannot be set because they have no prompt, are listed in ignored rather than refused, so an old defconfig still loads after the tree changes.

Method Does
set(name, value), unset(name) Add or remove a choice; the value is checked against the option's type at once
restore(choices) Replace the choices
resolve() Return the Configuration, or raise ConflictError with the conflict
explain(name) Return why the option has its value, as reasons
conflict(name, value) Return what stops the option taking the value, or None
fixes(name, value, limit) Return the smallest sets of other choices to drop that allow it, with the values each set changes
minimal() Return the choices without those that change nothing
set_all(value) Set every option the rules allow to y or n
save(defconfig, config) Resolve, then write both files; nothing is written on a conflict
dimacs() Return the clauses of the tree in DIMACS form

Files are written atomically and keep their existing mode. read_defconfig, write_defconfig, read_config and write_config handle the two file formats on their own.

Errors derive from ConfigError:

Error Raised when
ParseError A Kconfig file cannot be read or breaks the grammar
RejectedConstruct A Kconfig file uses a construct outside the subset
TreeError The tree parses but breaks a rule, such as an undefined symbol or a dependency cycle
InvalidValue A value has the wrong type, is out of range or names no option
ConflictError The choices cannot all hold; error.conflict holds the details
SolverUnavailable The picosat backend was asked for and cannot be loaded

The command

trellis menu                       # full-screen menu
trellis old                        # ask only about options config.pyb lacks
trellis olddefconfig               # write config.pyb from the defconfig
trellis defconfig configs/small    # take the choices of another defconfig
trellis savedefconfig --output -   # print the smallest defconfig
trellis set NET=y BUF=16           # add choices
trellis allyes                     # or allno
trellis check                      # exit 1 if config.pyb is out of date
trellis explain NAME [VALUE]       # why NAME has its value, or cannot be VALUE
trellis export-dimacs              # the clauses, for other SAT tools

--kconfig, --defconfig and --config name the three files, by default Kconfig, defconfig and config.pyb in the current directory. --solver chooses builtin or picosat. menu, old, set, defconfig, allyes and allno save both files when they succeed; olddefconfig writes only config.pyb. The time the solver took goes to standard error. The exit status is 0 on success, 1 when the choices conflict or a check fails, and 2 for an error in a file or an argument.

In the menu, Enter opens a submenu or edits a value, Space cycles a bool or tristate option, y, m and n set it, ? shows its help and why it has its value, and a also shows the options whose dependencies do not hold. A change that conflicts is undone and the menu lists the conflict with up to three fixes; 1 to 3 applies one. s saves and q quits.

The Kconfig subset

Trellis reads config, menuconfig, choice, menu, if, comment, mainmenu and source, with the attributes bool, tristate, int, hex, string, def_bool, def_tristate, prompt, default, depends on, select, imply, range, visible if, optional, modules and help. Anything else is refused with its file and line, never skipped. The differences from Linux:

  • source takes a path relative to the directory of the top file and cannot leave it. rsource, osource, option env, option defconfig_list and macros are refused.
  • Choices are bool only; tristate choices are refused.
  • Comparisons with = and != take bool and tristate options or the constants y, m and n. <, >, <= and >= are refused.
  • Defaults and ranges of int, hex and string options are literals.
  • Using an undefined symbol, defining a symbol twice, or a cycle through depends on and select is an error.
  • At most one option carries modules, and it must be bool.
  • Selecting an option whose dependencies fail is a conflict, not a warning.
  • Files, blocks, menuconfig levels and expressions nest at most 100 deep, and the conditions of one option at most 200 deep.

The meaning otherwise follows Linux. m counts as y for a bool option, and for a tristate option when modules are off. A default, an imply and the default member of a choice are preferences: the user's choices come first, and a preference gives way when it cannot hold with them. An optional choice starts with no member chosen.

How resolving works

The tree becomes clauses over two variables per tristate option and one per bool option. The user's choices are assumptions. The preferences are then tried in the order of the tree, each kept if everything kept so far can still hold, except that an option the user set keeps the user's value rather than its default. Last, every bool and tristate option is made as small as it can be. Each step fixes something no later step can undo, which is why the result does not depend on the solver. int, hex and string options are filled in once the others are fixed.

When the assumptions cannot hold, the solver's core is shrunk until removing any part of it would make the rest hold, so a conflict lists only what matters. Fixes are minimal correction sets, as ConfigFix proposes for Linux: smallest groups of the user's choices whose removal lets the wanted value hold.

Layout

Module Role
language/lexer.py Splits Kconfig text into lines and tokens
language/parser.py parse_file and parse_text: the subset, into an OptionTree
language/checker.py check: undefined symbols, duplicates, cycles and types
logic/expr.py Kconfig expressions and their values
logic/cnf.py Clauses and DIMACS output
logic/encode.py The tree as clauses, rules and preferences
solver/cdcl.py The built-in CDCL solver
solver/picosat.py The PicoSAT binding
resolve.py Choices into a Configuration
explain.py Conflicts, reasons and fixes
files.py defconfig and config.pyb
api.py Configurator
ui/line.py The line prompter of trellis old
ui/menu.py The full-screen menu
ui/text.py Text shared by the consoles
errors.py ConfigError and its subclasses
cli.py The trellis command

License

GPL-2.0-only. See LICENSE.

Metadata

Release files for trellis-kconfig 1.0.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 trellis-kconfig 1.0.0
File Size Uploaded Provenance Actions
trellis_kconfig-1.0.0.tar.gz 69.3 kB With provenance Details for trellis_kconfig-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for trellis-kconfig 1.0.0
File Interpreter ABI Platform Actions
trellis_kconfig-1.0.0-py3-none-any.whl Python 3 none any Details for trellis_kconfig-1.0.0-py3-none-any.whl

Total release size: 131.4 kB

Release files / trellis_kconfig-1.0.0.tar.gz

Download URL trellis_kconfig-1.0.0.tar.gz
Size 69.3 kB
Tags Source
SHA-256 checksum
How to use checksums
8f3612ab053d5fe4de40cde2fabd89998ca1197a40425370eeee17bbc67a6e6b
BLAKE2b-256 checksum
How to use checksums
2a6ea4d2a63dc5ca3414551ea86ae56dde04b18037026f187f078f5f1809d389
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / trellis_kconfig-1.0.0-py3-none-any.whl

Download URL trellis_kconfig-1.0.0-py3-none-any.whl
Size 62.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4a4d7b0204a804fccd0c512326ffb2da34fa0ba88921a77b26c72c7ac2062471
BLAKE2b-256 checksum
How to use checksums
dcb02bc001aef20f8f5bd868e72b1c6ca6bae9c2215302d890f3e0c42b9d94a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Security for trellis-kconfig 1.0.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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for trellis_kconfig-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full 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