Building a project to discover its file list (--allow-build)¶
See also: Command line for every other subcommand and flag --
this page covers --allow-build/--no-build-isolation/--build-timeout
only, split out because of its own size.
Available on project/generate/embed-wheel only (not wheel/enrich/
env/model, which never rescan a project directory).
By default, Pitloom's file discovery is a static read of a project's
build-backend config (Hatchling, setuptools, Poetry, PDM, Flit) -- it
never executes the project's own build. For a backend with no static
introspection at all (currently: uv_build), or when a supported
backend's own static discovery fails on a given project, Pitloom falls
back to a Hatchling-based heuristic and prints a WARNING: -- the file
list may be inaccurate in that case.
--allow-build opts into a more accurate but heavier alternative:
Pitloom actually invokes the project's own PEP
517 build backend (in a subprocess,
via build) and reads the resulting
wheel's real file list. This is a security-relevant decision --
it executes third-party build-time code from the project being scanned
-- so:
- It's off by default and must be passed explicitly every time; there is
no
[tool.pitloom]config-file equivalent, unlike every other flag in this guide. A target project's ownpyproject.tomlmust never be able to silently opt itself into code execution for whoever scans it. - Only enable it for a project whose build script you trust.
- Requires the optional
pitloom[build]extra (pip install pitloom[build]). - By default, the build runs in an isolated temporary environment
(installs the project's own
[build-system] requires, may hit the network -- reuses pip's normal cache across runs).--no-build-isolationskips this and uses the current environment's already-installed backend instead (faster, no network); it has no effect without--allow-build(logs aWARNING:if passed alone). --build-timeout DURATIONcaps how long the build may run -- see "Timing out a build" below.- The build's own stdout/stderr are captured to a log file rather than
leaking onto Pitloom's own stdout/stderr. A failed build's last output
line is part of its
WARNING:; on failure or timeout,loom --debugalso shows the end of the build output (up to 8 KiB) atDEBUG:. The build's stdin is closed (/dev/null-equivalent), so a backend that waits on stdin gets EOF instead of hanging. - On any failure (network unavailable, backend not installed, build
script error, timeout), Pitloom falls back to the same static file
discovery used without the flag -- usually the Hatchling heuristic;
where static discovery is unsupported (e.g. a
setup.py-only project with no[project]table), the SBOM lists no files, with aWARNING:.--allow-build's worst case is never worse than leaving it off. - It changes the file list, so which AI models are found can differ from a run without it; see Settings that change the SBOM.
- On
generate, all three flags parse for every target (generateauto-detects env/wheel/model-file/Hugging-Face/project targets from one shared parser) but only take effect when the target resolves to a project directory -- for any other target, including an sdist archive (whose file list comes from the archive's own listing, not a build), they're a no-op and Pitloom prints aWARNING:saying so. The same applies toembed-wheelwithout--project-dir(for the library, noproject_dir) -- the current directory is never used as the project -- or when--sbomsupplies an already-generated SBOM to embed verbatim.
loom project . --allow-build -o sbom.json
loom project . --allow-build --no-build-isolation -o sbom.json
loom project . --allow-build --build-timeout 15m -o sbom.json
Timing out a build¶
--build-timeout DURATION bounds how long the real PEP 517 build (see
above) may run before Pitloom kills its whole process tree and falls
back to static discovery. No effect without --allow-build.
DURATION is a bare ASCII integer (seconds, e.g. 900), or h/m/s
units written largest-first, each used at most once (900s, 15m,
1h30m, 1h30m45s) -- the same grammar as Go's time.ParseDuration/
Prometheus durations restricted to whole seconds. Rejected: decimals
(1.5h), ms/d units (500ms, 1d), uppercase, and whitespace.
Range: 1 second to 7 days (604800); default 20 minutes (1200) when the
flag is omitted.
On expiry, Pitloom terminates the build's whole process tree (not just the immediate child process) and logs:
WARNING: Build: build-and-read for <dir> timed out after <N>s (--build-timeout) -- build process tree terminated
-- or ... -- could not confirm the build process tree terminated when
a process in the tree was still alive after the kill (on Windows: when
taskkill did not report success). Either way Pitloom then falls back
to static file discovery, as without --allow-build (see above); the
command still exits 0 and still writes an SBOM, just with a potentially
less accurate or empty file list. Terminating the tree can take up to
about 8 seconds past the deadline on Linux/macOS (a grace period after
SIGTERM, then SIGKILL), and up to about 90 seconds on Windows.
Ctrl-C, SIGTERM and SIGHUP sent to Pitloom during the build (e.g. a CI
job cancellation, an external timeout(1) around loom, a closed
terminal) also terminate the build's process tree and remove its
temporary directories first; a signal that arrives while a temporary
directory is being removed waits for that removal to finish. The same
holds after the build, for as long as its files are in use (hashing,
AI-model scanning, every wheel of an embed-wheel batch): the
extracted-wheel temporary directory (pitloom-build-and-read-* in the
system temp directory) is removed at once, without waiting for the
current step to finish. Then:
- Ctrl-C (SIGINT) stops Pitloom with
ERROR: interruptedand exit status 130, as for any other command (see Debugging for the traceback). - SIGTERM/SIGHUP log
WARNING: Build: received SIGTERM during the build -- exiting after cleanup(or... after the build -- ...once the build has finished and its files are being read) and re-raise the same signal, so the exit status still reads "killed by that signal" (143 for SIGTERM in a shell). Running as PID 1 in a container (no--init), where the kernel ignores an unhandled SIGTERM, Pitloom exits with status 143 instead.
Either way no SBOM is written. Processes a successful build leaves
running in the background are killed before its temporary directory is
removed (Linux/macOS only), with INFO: Build: killed processes the
build left running (process group <N>) -- or a WARNING: Build: could
not confirm the processes the build left running (process group <N>)
terminated when some survived the kill (on Linux, e.g. one running as
another user; macOS reports such a survivor the same way as a group
with only exited processes left, so it goes unreported there).
Known limitations:
- The build runs in its own process session, so it is orphaned when
Pitloom dies without running that cleanup: on SIGKILL (e.g.
timeout -s KILL, or a harness that escalates to it), or when the library API is called from a non-main thread or from an application that installs its own SIGTERM/SIGHUP handler (Pitloom then leaves signal handling to that application). A build backend process that callssetsid()itself also escapes Pitloom's kill. - Once Pitloom no longer needs the build's files (the SBOM document is being assembled and written), SIGTERM/SIGHUP get their default handling back: there is nothing left to clean up.
- Windows: SIGTERM cannot be delivered from outside at all, and a
forced termination (
taskkill /F, a job-object kill), closing the console window, logoff or shutdown cannot be intercepted. Ctrl-Break is handled like SIGTERM above (the build receives it too, as it shares Pitloom's console); after the build, a file still open at that moment cannot be deleted on Windows, so the build's temporary directories may be left behind (aWARNING:names each one). Descendants of a build child that has already exited -- including background processes a successful build leaves running -- may not be reachable.
From Python¶
Pass build_options=BuildOptions(allow=True) to
generate()/generate_project_sbom() to let Pitloom invoke a project's own
PEP 517 build backend to discover its real file list, when static discovery
has no module for the backend (e.g. uv_build) or a supported backend's own
static discovery fails. The security rationale above applies identically.
Unlike use_lockfile=, it has no [tool.pitloom] config-file equivalent
and must be passed explicitly every call: there is no
config layer for a scanned project to silently opt itself into.
from pathlib import Path
from pitloom import BuildOptions, generate
generate(
Path("/path/to/project"),
output_path=Path("sbom.spdx3.json"),
build_options=BuildOptions(allow=True, timeout=900),
)
BuildOptions mirrors the three CLI flags:
| Field | CLI flag | Default |
|---|---|---|
allow (bool) |
--allow-build |
False |
no_isolation (bool) |
--no-build-isolation |
False |
timeout (int seconds or None) |
--build-timeout |
None (1200 s) |
timeout is plain int seconds -- not a duration string; the CLI's
own h/m/s grammar is a CLI/Action-layer convenience only.
BuildOptions(...) validates its fields when constructed, for every
target: a non-bool allow/no_isolation (e.g. the string "false")
or a non-int timeout raises TypeError; a timeout outside 1 to
604800 raises ValueError. A field set where it has no effect -- a
target other than a project directory, or no_isolation/timeout
without allow -- logs one WARNING: Build: ... line per field,
naming the field by its CLI flag spelling (e.g. --build-timeout)
followed by "has no effect" and the reason -- even when BuildOptions
was constructed directly and no CLI flag was ever typed. Intentional:
the same warning stays recognisable and grep-able regardless of which
surface (CLI, library API, ...) triggered it. Logged as early as
possible on every surface -- before any project-metadata or lock-file
WARNING: a project directory target might also trigger -- so it's
never buried later in a run's output.
See also¶
- Command line -- every other subcommand and flag.
- Python API -- the rest of the library API.
- GitHub Action -- the
allow-build/no-build-isolation/build-timeoutAction inputs.