Integrating adsmd
Ways to use adsmd inside a workflow. The command is the same everywhere (adsmd validate <plant-dir> plus flags); this page covers what goes around it.
Per-project tuning: .adsmd.toml
Put .adsmd.toml in the plant project directory or in any directory above it. adsmd validate walks up from the project and uses the first file it finds. The [rules] table switches rules off or changes their severity, so a project does not need --rule on every call.
[rules]
R13 = "off" # silence "ZProcedure not in the coded-step index" notes
R30 = "off" # silence the StaticPIF.txt note
R32 = "warning" # raise .bak/.orig/.pre artifacts from info to warning
R14 = "error" # raise header/filename mismatches from warning to errorWith this file next to a fresh-checkout copy of tests/BV4, the result changes from 1 error(s), 1 warning(s), 166 info(s) to 1 error(s), 7 warning(s), 10 info(s).
Values are case-insensitive: off (also ignore or silence), info, warning (or warn), error. Keys are R<n>, r<n> or <n>. R0 (parse errors) cannot be configured. A file that names R0, holds an unknown value or is not valid TOML makes validate stop with exit code 64.
Rules that are switched off do not reach the output and do not count toward the exit code. Rules with a changed severity carry the new severity into the summary, --severity filtering and the --exit-on policy.
GitHub Actions and Code Scanning
The workflow in this repository
.github/workflows/adsmd.yml runs on pushes to main and mpi-devel and on pull requests, when files under tools/adsmd/ or the workflow itself change. On ubuntu-latest and macos-latest it runs, in tools/adsmd/: cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, cargo build --workspace --release and cargo test --workspace.
The test suite includes two snapshot tests that run adsmd validate --json on tests/BV4 and tests/BV3 and compare the per-rule counts with stored snapshots. R1, R2 and R33 are left out of the snapshots because they depend on the git-ignored ZiniADS.txt. The workflow does not upload a report.
Example: upload SARIF for a plant project
adsmd validate --sarif emits a SARIF 2.1.0 log, which GitHub Code Scanning shows as annotations in the pull request and in the Security tab. This job is an example; it is not part of the repository:
name: adsmd-report
on:
push:
branches: [main]
pull_request:
jobs:
adsmd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Run adsmd
working-directory: tools/adsmd
run: |
cargo run --release --quiet --bin adsmd -- \
validate ../../tests/BV4 --sarif > $GITHUB_WORKSPACE/adsmd.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: adsmd.sarifcargo run passes on the exit code of adsmd, so the "Run adsmd" step fails exactly when adsmd exits non-zero. By default that means an error is present. For tests/BV4 it always is (the R0 finding in SystemReliability.txt), so this job is red for BV4 and still uploads the log because of if: always(). Add --exit-on never for a job that reports without failing, or --exit-on warning to fail on warnings as well.
Pre-commit hook
A .git/hooks/pre-commit that blocks a commit while the project has errors:
#!/usr/bin/env bash
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
adsmd validate "${ADSMD_PLANT_DIR:-tests/BV4}" --severity errorMake it executable (chmod +x) and have adsmd on PATH. The hook fails whenever any error is present, not only for errors introduced by the commit. With the default tests/BV4 it therefore always fails (exit code 2, from the R0 finding); set ADSMD_PLANT_DIR to the project you maintain. For per-file scoping or SARIF artifacts, call the same command from pre-commit or a similar hook manager.
CMake and ctest
tests/CMakeLists.txt registers one test for adsmd when cargo is found at configure time:
find_program(CARGO_EXECUTABLE cargo)
if(CARGO_EXECUTABLE)
add_test(NAME BV4_Adsmd_Manifest
COMMAND ${CMAKE_COMMAND} -E env CARGO_TERM_COLOR=never
${CARGO_EXECUTABLE} run --quiet
--manifest-path ${CMAKE_SOURCE_DIR}/tools/adsmd/Cargo.toml
--bin adsmd --
manifest ${TESTS_TARGET_DIR}/BV4
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/adsmd)
set_tests_properties(BV4_Adsmd_Manifest PROPERTIES
TIMEOUT 600
LABELS "adsmd")
else()
message(STATUS "cargo not found; skipping adsmd integration test")
endif()Run it from the build directory:
ctest -R BV4_Adsmd_Manifest
ctest -L adsmdThe test builds adsmd and runs adsmd manifest on the copy of BV4 in build/tests/BV4. It passes when the command exits 0, which shows that the tool builds and that the project is recognised. manifest exits 0 even when files fail to parse, so this test does not gate on findings. A test that runs validate <plant-dir> instead would gate on errors; for BV4 it would always fail because of the R0 finding.
Shell completions
adsmd completions <shell> prints a completion script for bash, zsh, fish, powershell or elvish:
adsmd completions zsh > _adsmdMove the file to the place your shell loads completions from (for zsh, a directory on $fpath). The scripts complete subcommands, flags and the shell names of completions; plant directories complete as ordinary paths.
For diagnostics inside an editor, load the SARIF output into a SARIF viewer extension; the log carries file, line and column for every finding.
Standalone HTML report
adsmd validate <plant-dir> --html=report.html writes one self-contained HTML page (inline CSS and JavaScript, no external assets): severity chips, a filter bar with per-severity toggles and a rule selector, per-file grouping, source snippets with the offending line marked, and a link from each finding to its entry in the Rule Catalog.
adsmd validate ../../tests/BV4 --html=bv4.htmlOpen the file in any browser. The page works as a CI artifact, an attachment or an offline review copy. The exit code follows the same rules as for the other formats.
Container images
Docker/adsmd/Dockerfile defines two final stages, selected with docker build --target:
validator: an image whose entrypoint isadsmd, for running the tool on a mounted plant project without a Rust toolchain on the host.serve: an image that generates the HTML report from the project mounted at/plantwhen the container starts and then serves it with Python'shttp.server(port 80 inside the container). The environment variablesPLANT_DIR,REPORT_DIRandPORToverride the defaults.
The build and run commands are in Docker/adsmd/README.md.
Programmatic consumption: JSON
adsmd validate --json emits the diagnostic list and a summary block:
adsmd validate ../../tests/BV4 --json | jq -c '.summary'
# {"errors":1,"exit_code":2,"infos":166,"warnings":1}
adsmd validate ../../tests/BV4 --json \
| jq -r '.diagnostics[] | select(.severity == "error") | .message'The summary shown is for a fresh checkout. With a local tests/BV4/ZiniADS.txt the info count is 165, and a wrong input-deck path in that file adds a second error (see the tutorial).
adsmd manifest <plant-dir> prints the typed manifest as JSON and adsmd schema prints the JSON Schema that describes it. Together they are the contract for other tools: generate types from the schema, then read the manifest and the diagnostics.
Picking the exit-code policy
| Goal | Flag |
|---|---|
| Fail only on errors | none (same as --exit-on error) |
| Fail on warnings too | --exit-on warning |
| Fail on any diagnostic | --exit-on info |
| Report without failing | --exit-on never |
Filters act before the exit code is computed: a diagnostic removed by --severity, --rule or .adsmd.toml does not count. Combine the policy with .adsmd.toml severity changes to decide, per project, what counts as a warning and what as an error.