This release is a pre-release and may not be stable for production use.
bbtidy
Experimental formatter, linter, and lexer for BitBake metadata.
Description
bbtidy is an experimental tool for formatting and inspecting BitBake recipes
and configuration files. It provides a lexer, a conservative formatter for
top-level metadata assignments, and file-local linting suitable for CI.
Features
- Tokenization: efficiently breaks down BitBake files into tokens (Identifiers, Strings, Keywords, etc.) using
logos. - Span Reporting: Reports the exact location (line and column) of each token.
- Lossless concrete syntax tree: Represents every top-level byte as a source-backed node with stable ranges while retaining comments, blank lines, unknown syntax, and embedded bodies verbatim.
- Modern metadata syntax: Recognizes all eight assignment operators, colon overrides, key expansion, variable flags, multiline quoted values, and current BitBake directives.
- Safe formatting boundaries: Formats complete, single-line top-level assignments while preserving shell functions, Python functions, continued statements, and unsupported syntax.
- Fail-safe writes: Refuses to rewrite structurally incomplete input and replaces successfully formatted files atomically.
- Automation-friendly CLI: Provides explicit
format,check,lint, andlexcommands, standard-input support, unified diffs, and documented exit codes. - Layer-wide operation: Recursively discovers supported BitBake files in deterministic path order.
- Initial linting: Reports stable rule IDs, severity, line, and column for a focused set of reproducibility and metadata hygiene checks.
Installation
Install the native executable from PyPI with pip:
python -m pip install bbtidy
bbtidy --version
Because bbtidy is a command-line application, pipx install bbtidy is also a
good choice for an isolated global installation. The Python distribution does
not expose an importable module; it places the bbtidy executable in the
environment's scripts directory, following the same binary-wheel model used by
Rust-based Python tools such as Ruff.
Release wheels target:
- Linux x86-64 and ARM64 using glibc (
manylinux_2_17) - Linux x86-64 and ARM64 using musl (
musllinux_1_2) - macOS on Intel and Apple silicon
- Windows on x86-64
The wheels are independent of the Python interpreter implementation and support Python 3.8 or newer. Installing a matching wheel does not require Rust. A source distribution is also published as a fallback; building it requires Rust 1.85 or newer.
Usage
To inspect the tokens in a recipe:
bbtidy lex sample.bb
To print a formatted file without modifying it:
bbtidy format messy.bb
Standard input is accepted as -:
printf 'SUMMARY="Example"\n' | bbtidy format -
To inspect changes across a file or directory:
bbtidy format --diff recipes-example/
To check formatting in CI, then explicitly rewrite files when desired:
bbtidy check recipes-example/
bbtidy format --write recipes-example/
To lint a file, recipe directory, or layer:
bbtidy lint recipes-example/
Findings use an editor- and CI-friendly format:
recipes-example/example.bb:12:11: warning[BBT004]: SRCREV uses ${AUTOREV}; pin a source revision for reproducible builds
Formatting is intentionally limited while BitBake syntax support is being developed. Embedded shell and Python code is kept byte-for-byte unchanged.
Directory inputs are traversed recursively. .bb, .bbappend, .bbclass, and
.inc metadata files are discovered automatically, except beneath recipe
payload directories named files. .conf files are discovered inside a
layer's conf directory, identified by its layer.conf. An explicit file input
is always processed. Paths are sorted and deduplicated before processing.
Standard input must be the only input, and --write cannot be used with it.
format writes formatted source to standard output and requires one input
unless --diff or --write is selected. format --diff, lint, and lex can
process multiple inputs without changing them. Before format --write changes
any files, every input is read and formatted successfully; each changed file is
then replaced atomically.
Exit codes
0: the command completed successfully.1:checkfound formatting differences orlintfound diagnostics.2: command usage, input/output, lexing, formatting, or lint analysis failed.
Operational diagnostics are written to standard error. Lexer error tokens
remain part of the token stream on standard output and cause exit code 2.
format --diff returns 0 when it successfully reports differences; use
check when differences should fail a CI job.
Initial lint rules
| Rule | Name | Detects |
|---|---|---|
BBT001 |
trailing-whitespace |
Spaces or tabs at the end of a line |
BBT002 |
final-newline |
A non-empty file without a final newline |
BBT003 |
summary-length |
A static, literal SUMMARY longer than 80 characters |
BBT004 |
autorev |
SRCREV variants that use ${AUTOREV} |
BBT005 |
duplicate-inherit |
A static class inherited more than once in one file |
All initial rules are warnings. Diagnostics are sorted by source location and
exposed through the public lint, lint_rules, LintDiagnostic, LintRule,
and LintSeverity Rust APIs. Structurally incomplete input is reported as an
operational error instead of producing potentially misleading findings.
The semantic rules are intentionally conservative: they inspect top-level metadata, skip embedded shell and Python bodies, and avoid evaluating dynamic values or class names. Cross-file inheritance, configuration, suppression, and release-specific analysis remain future work.
Lossless syntax tree
The Rust library exposes parse as the shared structural front end for
formatting and linting. Its SyntaxTree borrows the original source and divides
it into ordered, contiguous SyntaxNode ranges. Concatenating node.text() for
every node always reproduces the input byte-for-byte.
Recognized nodes provide structured data and absolute source ranges for assignments, directives, shell and Python functions, and top-level Python definitions. Blank lines, comments, and unsupported top-level constructs remain explicit nodes. Function and Python-definition bodies are deliberately opaque: bbtidy finds their safe boundaries but does not interpret or rewrite the embedded language.
use bbtidy::{SyntaxKind, format_syntax, lint_syntax, parse};
let source = "SUMMARY=\"Example\"\n";
let tree = parse(source)?;
if let SyntaxKind::Assignment(assignment) = tree.nodes()[0].kind() {
assert_eq!(assignment.name(), "SUMMARY");
}
let formatted = format_syntax(&tree);
let diagnostics = lint_syntax(&tree);
# Ok::<(), bbtidy::SyntaxError>(())
format and lint are convenience entry points that parse source and then
delegate to format_syntax and lint_syntax. Callers performing multiple
operations can parse once and reuse the same tree.
Supported syntax
The 0.1.0-alpha.2 lexer recognizes:
- Assignments using
=,:=,?=,??=,+=,=+,.=and=. - Literal and dynamic overrides such as
RDEPENDS:${PN}:class-native - Key expansion such as
A${B}and variable flags such asdo_fetch[network] include,include_all,require,inherit,inherit_defer,addfragments,addpylib,addhandler,addtask,deltask,EXPORT_FUNCTIONS,exportandunset- Single- and double-quoted values, including multiline values
Legacy underscore overrides remain lexically accepted as identifier and variable-reference components. They are not interpreted as override operations. The formatter remains deliberately conservative and only changes complete, single-line top-level assignments.
Development
Run the test suite with:
cargo test
Build and verify the Python wheel and source distribution with:
maturin build --release --locked --sdist --out dist
python scripts/smoke_test_package.py --kind wheel dist
python scripts/smoke_test_package.py --kind sdist dist
pip install . uses the same PEP 517 configuration for a local source build.
The Cargo version is the release source of truth; maturin converts prereleases
to PEP 440 automatically, for example 0.1.0-alpha.2 becomes 0.1.0a2.
The integration suite includes a representative fixture layer containing
.bb, .bbappend, .bbclass, .inc, and .conf files. It verifies golden
output, idempotence, byte-for-byte preservation of embedded code, structured
errors, lint rule behavior, CLI modes and exit codes, deterministic directory
handling, and the no-write guarantee for malformed input.
Upstream compatibility corpus
The extended compatibility check uses commit-pinned snapshots of
OpenEmbedded-Core and the meta-oe, meta-python, and meta-networking
layers. The revisions and minimum corpus sizes are recorded in
tests/upstream-corpus.json; upstream repositories are downloaded into a
temporary workspace and are not vendored in this repository.
On a supported Linux build host with the standard Yocto host packages installed, run the complete check with:
cargo build --release --locked
python scripts/check_upstream_corpus.py
The harness scans more than 3,300 real metadata files, formats a disposable
copy, verifies idempotence, exercises lint analysis, checks that embedded
functions and Python blocks remain byte-for-byte unchanged, and confirms that
recipe payload files were not touched. It then initializes a disposable Poky
build and parses core-image-minimal with all four formatted layers.
Existing pinned checkouts can be reused, and the BitBake parse can be omitted on a non-Linux development machine:
python scripts/check_upstream_corpus.py \
--source-root /path/containing/poky-and-meta-openembedded \
--skip-bitbake
The upstream compatibility workflow runs for relevant pull requests and pushes
to main, as well as on a weekly schedule. A compatibility failure should be
reduced to a focused local regression test before the pinned snapshot is
advanced.
To validate the formatted corpus with a real BitBake parser, use a disposable build whose environment has already been initialized:
bbtidy_repository=/absolute/path/to/bbtidy
bitbake-layers add-layer "$bbtidy_repository/tests/fixtures/corpus/expected"
BBTIDY_BITBAKE_BUILD_DIR="$BUILDDIR" \
"$bbtidy_repository/scripts/check-bitbake-parser.sh"
The script is opt-in because BitBake is not a project dependency. It invokes
bitbake --parse-only example, ensuring the corpus recipe is available and
parseable, and does not edit the selected build configuration.
Releasing to PyPI
The Python package workflow builds and installs a wheel on every pull request
and push to main. The release workflow can be run manually to inspect all
platform artifacts without publishing.
To publish a release:
- Update the version in
Cargo.tomland finalize the changelog. - Create a tag that exactly matches the Cargo version, such as
v0.1.0-alpha.3. - Push the tag. The release workflow builds and smoke-tests all wheels and the source distribution, then publishes them through PyPI Trusted Publishing.
Before the first automated publication, configure the bbtidy project on PyPI
with GitHub owner jorisguex, repository bbtidy, workflow
publish-pypi.yml, and environment pypi. No repository API token is needed.
The version guard rejects mismatched tags before any release artifacts are
built.
License
This project is licensed under the MIT License.
Metadata
Release files for bbtidy 0.1.0a2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bbtidy-0.1.0a2.tar.gz | 49.5 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| bbtidy-0.1.0a2-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| bbtidy-0.1.0a2-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| bbtidy-0.1.0a2-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| bbtidy-0.1.0a2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| bbtidy-0.1.0a2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| bbtidy-0.1.0a2-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| bbtidy-0.1.0a2-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details |
Total release size: 3.4 MB
Release files / bbtidy-0.1.0a2.tar.gz
| Download URL | bbtidy-0.1.0a2.tar.gz |
|---|---|
| Size | 49.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4320e708f282eb3ce5688053f73576f03ce2f90a8692ea5095e5e7e34be9f307
|
|
BLAKE2b-256 checksum How to use checksums |
662bddd55d4a026dd6b75789726fc1293cea26c8f0cf4f269624cf0cc9854fd6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 2, 2026.
Transparency logRelease files / bbtidy-0.1.0a2-py3-none-win_amd64.whl
| Download URL | bbtidy-0.1.0a2-py3-none-win_amd64.whl |
|---|---|
| Size | 380.0 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
e49babaa1df5f3ca515a9772a37203991f5fc3d9b602b03bf397d18523c227e6
|
|
BLAKE2b-256 checksum How to use checksums |
9727cc8e0211567ee8713f9cebdea2d3d012197309059ec970484a3558eda669
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 2, 2026.
Transparency logRelease files / bbtidy-0.1.0a2-py3-none-musllinux_1_2_x86_64.whl
| Download URL | bbtidy-0.1.0a2-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 530.6 kB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
e78cb829633da10c6ecdff152f04c003e08033dde6cdcf2bc0f784541e50a726
|
|
BLAKE2b-256 checksum How to use checksums |
d6b92fe9b91b9fcf782a8a4a6b053f5fa8f69798deb3adb6b108e0da33935c2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 2, 2026.
Transparency logRelease files / bbtidy-0.1.0a2-py3-none-musllinux_1_2_aarch64.whl
| Download URL | bbtidy-0.1.0a2-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 498.2 kB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
0061ba41326b8c3f0886b51ced980fcce1007b0b0fdf842292708ceecc30750d
|
|
BLAKE2b-256 checksum How to use checksums |
0da658715a12d39424743f2c93c382d4958c78101c49c61ad34109ae41c80f8d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 2, 2026.
Transparency logRelease files / bbtidy-0.1.0a2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | bbtidy-0.1.0a2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 484.4 kB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
0fcaa16cf3067455d2fbd3bab740a0e67268d86ebd04e876d4ba14454fa37cde
|
|
BLAKE2b-256 checksum How to use checksums |
b9882620c6e618243f88d4cfa36e7491ca77f5af860a93f24241183e4feb2898
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 2, 2026.
Transparency logRelease files / bbtidy-0.1.0a2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | bbtidy-0.1.0a2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 453.7 kB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
03ea8b18a3a8ebb6180a9ddb8354ebda6640ec65ab83bd45f338fcc1a2af4a37
|
|
BLAKE2b-256 checksum How to use checksums |
1cba04f0fc13f8cda5a1f4ec56e79f5365cd5accc900724a5c593133289f0e2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 2, 2026.
Transparency logRelease files / bbtidy-0.1.0a2-py3-none-macosx_11_0_arm64.whl
| Download URL | bbtidy-0.1.0a2-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 440.7 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
475c291f11f36e60b615fb6b99b498b52f6b51a7d563e92ce78a8e04f8365ac9
|
|
BLAKE2b-256 checksum How to use checksums |
8bdb9be8643598b46e75e3b6b24dc69d93c477a5871f529f396eaacfabab0b52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 2, 2026.
Transparency logRelease files / bbtidy-0.1.0a2-py3-none-macosx_10_12_x86_64.whl
| Download URL | bbtidy-0.1.0a2-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 519.5 kB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
a45c86ddeb30e9fd471c0529f045d488750e358c77205abb9e2a6c6e8ae89dba
|
|
BLAKE2b-256 checksum How to use checksums |
2d81ab018361fa6c92fe6fcd65df0c40ec22a931dd2270f52603ba77985e2b00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 2, 2026.
Transparency log