|
TRUST 1.9.8
HPC thermohydraulic platform
|
This doc describes how trustify decides which TRUST sources to scan (--trust-root) and which baltik projects to overlay on top (--projects) when you run a command. The same logic applies whether you call the CLI (trustify <cmd>) or the programmatic API (from trustify import generate_schema, ...), but the CLI does some extra work behind the scenes that the API doesn't — see CLI vs programmatic API.
For each of the two project-list fields (projects, trust_root), trustify consults up to four sources, in strictly decreasing priority:
| # | Source | When it kicks in |
|---|---|---|
| 1 | CLI flag (--projects / --trust-root) | always wins when present |
| 2 | .trustify.json in an ancestor of the current path | nearest one wins; CLI auto-discovers, API on opt-in |
| 3 | Baltik auto-detection via project.cfg in an ancestor | only for --projects; CLI auto-detects, API on opt-in |
| 4 | Env var ($TRUST_ROOT / $project_directory) | always consulted last |
Crucially: each field is resolved independently. A .trustify.json that declares only projects does NOT prevent $TRUST_ROOT from filling in trust_root. This is the "per-field strict-replace" rule — once a higher-priority source sets a field, no lower-priority source can change it; but absent fields cascade down to the next layer.
You source the TRUST env ($TRUST_ROOT and $project_directory are set), cd into your dataset folder where you have a .trustify.json declaring projects: ["/abs/baltik-A", "/abs/baltik-B"] but no trust_root. You run trustify check foo.data:
Effective schema scan: $TRUST_ROOT + baltik-A + baltik-B.
The config file is a JSON object with three optional top-level sections — workspace (project resolution), cache, and lsp. Every field within each section is optional. Newer keys may be added later; unknown keys are preserved verbatim under raw for the LSP and future consumers.
Two intended use cases drive two placement patterns:
Developer inside a baltik (or TRUST itself). A project.cfg in the baltik root already identifies the baltik as the active project — you don't need to declare workspace.projects in .trustify.json. You might still want a .trustify.json for LSP customization (enum_dedup_threshold, ...) or to opt out of cache.auto_trim. Keep the file minimal:
Hard rule: a .trustify.json sitting next to a project.cfg MUST NOT declare workspace.projects or workspace.trust_root. The project is already auto-detected from the baltik marker; redeclaring it here is ambiguous and trustify rejects the combination with a ConfigError pointing at the offending file. Strip those keys, or move the .trustify.json outside the baltik tree if you intended it to override the auto-detected project.
Workspace .trustify.json files are usually personal: they encode your local install paths, not project policy. The TRUST repo's root .gitignore ignores .trustify.json so accidental copies don't get committed. The repo deliberately tracks one .trustify.json at the very root, used to ship the LSP defaults that apply to anyone working in core TRUST. Baltiks are encouraged to do the same.
Walking up from the relevant path, trustify treats the first directory that contains a project.cfg file as the active baltik. This is how trustify check $YOUR_BALTIK/datasets/foo.data knows to add your baltik to --projects without you having to set anything up.
Every directory passed as --projects (or listed under projects in a .trustify.json, or auto-detected) must be a proper baltik — i.e. it must contain a top-level project.cfg with [description].name set. bin/trust baltik and baltik_configure create this file when they scaffold a baltik. Pointing --projects at a bare directory now fails fast with a ConfigError rather than silently scanning it as an unnamed project: run baltik_configure inside the directory to fix it, or pass it via --trust-root if you actually meant to point at a TRUST source tree.
The baltik detector and the $project_directory env var are redundant on purpose. Detection is the more reliable path (works even if you didn't source the baltik's env), but the env var is kept as a backup for users who run trustify from outside any baltik checkout while still having $project_directory set from an earlier shell session.
Whenever any source other than a CLI flag contributed to the resolved workspace (auto-discovered .trustify.json, baltik auto-detection, $TRUST_ROOT / $project_directory env, or transitive [dependencies] expansion), the CLI prints a one-shot summary to stderr right after resolution:
The block is silent when every entry came from a CLI flag, and for the projects subcommand (which is itself a stdout dump of the same data).
When every path arg (or, in the no-path case, the CWD) sits inside the resolved $TRUST_ROOT and an implicit baltik signal (auto-detected project.cfg, or the $project_directory env-var fallback) supplied the overlay, trustify keeps the overlay but prints a one-line WARNING to stderr:
This used to be a hard suppression — both implicit signals were silently dropped so a stale $project_directory left over from a previous source <baltik>/env.sh could not overlay the wrong baltik. That also blocked the legitimate case: a baltik whose sources live inside the TRUST tree (e.g. ICoCo) is a perfectly valid overlay target when you want to check whether the baltik's XD tags break TRUST datasets. The call is now left to the user — the overlay applies and the warning makes an accidental stale env visible.
The explicit signals (--projects flag and a .trustify.json workspace.projects list) never trigger the warning — the user clearly asked for the overlay.
Once the (projects, trust_root) pair is decided, trustify expands each project's [dependencies] section transitively — same model as baltik_configure. A baltik whose project.cfg declares:
contributes its own keywords plus every keyword from solver_kit and shared, recursively. Paths support the same conventions as baltik scripts:
The resolved list is post-order: each dependency appears before the baltik that declares it, so the inner baltik shadows its deps in scanSourceFiles. Diamond dependencies are deduplicated; each unique resolved path is scanned exactly once.
Two rigid checks (mirroring baltik_configure):
trustify init-config <DIR> runs the full resolution chain (CLI flags → .trustify.json → baltik auto-detection → env vars → transitive dependencies) and writes the result into <DIR>/.trustify.json. The intended workflow:
After this, that directory carries a self-contained config: future trustify invocations from there work without any env setup, against the exact same project list that was active when you ran init-config. Same idea as pip freeze > requirements.txt for a Python env.
Because resolution validates every path, init-config refuses to write an "aspirational" config pointing at directories that don't exist — fix the projects, then re-run.
Two ways to disable transitive expansion (the resolved list is then exactly what the four-signal step produced, with no further expansion):
The CLI flag is intended for one-off invocations; the config switch fits workflows where you've pinned every baltik path explicitly in .trustify.json and don't want trustify second-guessing.
| CLI (trustify <cmd>) | Programmatic API (from trustify import ...) | |
|---|---|---|
| .trustify.json discovery | automatic, walks up from path args (or CWD if none) | opt-in — caller invokes discover_config(path) and threads result through |
| Baltik auto-detection (project.cfg) | automatic | opt-in — caller invokes detect_baltik_root(path) |
| $TRUST_ROOT / $project_directory fallback | yes | yes (via effective_* helpers, used internally) |
| Multi-path discovery anchor | per path arg, see below | N/A (single call site) |
The library API is side-effect-free by design: no implicit filesystem walks, no surprises. If you want CLI-style auto-discovery in a script, do it explicitly:
The returned TrustifyConfig also carries the cache and LSP settings, useful when the LSP needs to know e.g. cfg.lsp.enum_dedup_threshold.
trustify batch-check, trustify batch-format, and any other command that takes multiple paths use per-path discovery: each path arg is treated as an independent anchor for the upward walk. If they disagree — e.g. two paths under different .trustify.json files or under different baltiks — the CLI errors out with a clear message rather than silently picking one and running with mismatched context.
If you genuinely need to run trustify across two workspaces, do it as two separate invocations.
trustify projects dumps the full resolved workspace as a script-friendly list of paths — useful when another tool (Doxygen, a build system, a CI driver) needs the same dependency chain trustify computed.
Roles:
Errors (exit 2) when nothing can be resolved at all — matches every other schema-needing command. Filtering down to an empty list (e.g. --only=dependency on a baltik with no deps) is not an error; it prints nothing and exits 0.
The canonical use case is the baltik scaffold's docs/Makefile, which asks trustify for the list of baltiks this one depends on, so Doxygen's INPUT can be extended to include their C++ sources:
The four-signal resolver above applies to every command that reads sources for schema purposes — check, batch-check, generate_schema, generate_markdown, generate_keywords, init-config, projects.
trustify modernize is the one command that writes sources back in place, so its scope is narrower and bypasses the resolver entirely (rules live in cli/__init__.py::_scope_modernize):
The anchor-inside-$TRUST_ROOT warning applies here too: when CWD sits inside $TRUST_ROOT and an implicit baltik signal (auto-detected project.cfg or $project_directory) is active, modernize targets that baltik and prints a WARNING — it no longer suppresses the signal in favour of the whole TRUST tree. This is what lets you modernize a baltik whose sources live inside TRUST (e.g. ICoCo). The warning is sterner than the schema resolver's because modernize rewrites .cpp / .xd sources in place, so a stale $project_directory from a previous source <baltik>/env.sh could rewrite the wrong tree — but, per the "leave it to the user" policy, it warns rather than silently dropping the overlay.
Note modernize's scope is decided entirely here, in the CLI: api.modernize / modernize._resolve_src_dirs do NOT re-apply the $project_directory / $TRUST_ROOT env fallbacks. Passing trust_root=None means "do not include TRUST", even when $TRUST_ROOT is set in the shell — otherwise modernizing a single baltik would fold in all of TRUST.
trustify reads project.cfg files via Python's configparser, while baltik_configure parses them through a hand-rolled sed / awk / eval pipeline. The two have meaningfully different default semantics — trustify configures configparser to match baltik_configure on the cases that occur in practice. This appendix is the reference for anyone debugging a "baltik accepts this, trustify doesn't" (or vice versa) report.
The parsing primitives live in bin/baltik/share/baltik/bin/baltik_configuration_parsing:
Dependency-graph traversal lives in bin/baltik/share/baltik/bin/baltik_dependencies_management (add_dependencies recurses, check_dependencies errors on same-name-different-path).
The behaviour that matters for trustify parity:
| baltik_configure behaviour | Mechanism |
|---|---|
| Strip inline # comments on every line | sed 's/#.*//' |
| = and : are interchangeable separators | sed 's/ *[:=][ \t]*/:/' |
| Section ends at the first empty line (not next [header]) | sed -n "/^$2$/,/^$/ p" |
| [DEFAULT] is just a regular (ignored) section | sed treats it like any other header |
| Field-name lookup is case-sensitive substring match | sed -n "/$2/p" |
| Field value is the second :-split column | cut -d : -f 2 |
| Continuation lines ( indented) are not interpreted | each line stands alone |
| Dependency value resolved via shell eval "echo …" | shell power: $(...), backticks, quoting, concat |
| Dependency path resolved relative to declaring baltik's CWD | cd ${dependency_orig_path} before the cd $declared_path |
| Two distinct paths with the same [description].name is an error | check_dependencies → too_many_paths_error |
| Declared dep name must match the dep's own [description].name | invalid_dependency_name_error |
| Unknown [description] fields rejected | whitelist: name\|author\|executable\|kernel\|cpp_flags\|ld_flags |
Implemented in src/trustify/projects.py (_read_baltik_config, _resolve_dep_path, expand_dependencies):
These corner cases are not chased bug-for-bug; either nobody writes them in practice, or trustify is strictly better.
If a real-world project.cfg ever surfaces that baltik accepts but trustify rejects (or vice versa):