Purpose
-------
Remove the ``build/`` and ``dist/`` trees that Python packaging tools
leave behind, for :command:`oct clean`.
These are not merely untidy. A stale ``build/lib/<pkg>`` is a complete
second copy of a package's source, and on 2026-09-26 it cost real time
twice in one session: ``oct docs`` auto-discovery ran ``sphinx-apidoc``
over both the live package and the build copy, and Doxygen's recursive
scan ingested ``templates/claude_skills/*/SKILL.md`` from both, putting
16 duplicate pages in one sidebar. Anything that walks a project tree
sees the copy, and the copy is arbitrarily old.
Responsibilities
----------------
- Provide :func:`clean_build_dirs`, matching the ``(removed, failed)``
contract of the cleaners in :mod:`oct.tools.source_exporter`.
- Refuse to delete a ``build`` or ``dist`` directory that does not
demonstrably hold packaging output, and say so rather than passing
over it in silence.
Diagnostics
-----------
Domain: OCT-CORE
Levels:
L2 - sweep entry / exit
L3 - per-directory decision
L4 - shape-check detail
Contracts
---------
- Name matching alone NEVER authorises a delete. ``build`` and ``dist``
are ordinary English words and a project is entitled to a source
directory called either. Every candidate must also (a) sit directly
beside a ``pyproject.toml`` or ``setup.py``, so it belongs to a Python
distribution root, and (b) contain nothing but the entries the
packaging tools themselves produce. A candidate failing either test is
left alone with a printed note.
- An empty ``build``/``dist`` beside a distribution root IS removable:
the tools create the directory before filling it, so an interrupted
build can leave it bare.
- Removal failure is reported, never raised. :command:`oct clean` sweeps
several categories and one unremovable tree must not abort the rest.
Dependencies
------------
- oct.core.exclusions (COMMON_EXCLUDE_DIRS - directories not worth walking)
- oct.core.diagnostics (_dbg structured logger)
- oct.tools.fs_remove (remove_tree_force)
| tuple[bool, str] build_artifacts._looks_like_packaging_output |
( |
Path | target | ) |
|
|
protected |
Return ``(is_packaging_output, reason)`` for a ``build``/``dist`` dir.
*reason* is meaningful only when the answer is False, and distinguishes
two cases the caller reports differently:
- **Empty string** - never a candidate. No distribution marker sits
beside it, so the name is a coincidence and there is nothing to
tell the operator. Most directories called ``dist`` in a tree with
any JavaScript in it land here, and announcing each one would bury
the real output.
- **Non-empty** - it WAS a candidate (marker present) and was refused
on its contents. That is worth printing: it names the entry that
stopped the delete, so the operator can see whether the refusal was
right.
Definition at line 94 of file build_artifacts.py.
| tuple[int, list[Path]] build_artifacts.clean_build_dirs |
( |
Path | root, |
|
|
bool | dry_run = False ) |
Remove packaging ``build/`` and ``dist/`` trees under *root*.
Parameters
----------
root
Directory to sweep. Walked recursively, skipping the usual
uninteresting directories (``.git``, ``.venv``, ``__pycache__``
and the rest of :data:`COMMON_EXCLUDE_DIRS`) plus
:data:`_EXTRA_PRUNE_DIRS`.
dry_run
Report what would be removed without touching the filesystem.
Returns
-------
tuple[int, list[Path]]
``(removed, failed)`` - *removed* counts directories that are
gone (or would be, under *dry_run*); *failed* lists those still
present afterwards. Directories refused by the shape check are in
neither: nothing was attempted on them, so counting them as
failures would misreport a correct refusal as a problem.
Definition at line 136 of file build_artifacts.py.