"""Draws the quality-assurance pipeline overview of community/quality_assurance.md as an inline SVG into docs/images/qa_pipeline.svg. The SVG is not an image: the page includes its text with `--8<-- "images/qa_pipeline.svg"`, so it picks up the colours and fonts of the page (see the `nj-fig` rules in docs/css/brand.css and the `nj-exceptions-*` rules in docs/css/fig-exceptions.css). The file holds two drawings, a three-column one and a one-column one for phones; the CSS shows one of them. The lanes, tools and counts are taken from the text of community/quality_assurance.md, the trigger badges from the `on:` sections of .github/workflows/*.yml. Update LANES when the page changes, run `python3 scripts/figures/qa_pipeline.py` from docs/mkdocs, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the abbreviation extension wraps it in an and the text disappears. """ import html import re from pathlib import Path OUT = Path(__file__).resolve().parents[2] / "docs" / "images" P = "nj-exceptions-" # trigger badges: key -> (label, css modifier) TRIGGERS = { "push": ("every push / PR", "push"), "pr": ("every PR", "push"), "sched": ("scheduled", "sched"), "cont": ("continuous", "cont"), } # title, triggers, lines (at most 31 characters, so they fit a card of the three-column drawing) LANES = [ ("Build matrix", ["push"], [ "50+ compilers: GCC, Clang, MSVC,", "AppleClang, Intel, NVIDIA, ...", "C++11, 14, 17, 20, 23, and 26", "libc++ and libstdc++", "warnings: Clang -Weverything,", "GCC 300+ warnings", ]), ("Unit tests (doctest)", ["push"], [ "every public API function", "every exception id and message", "all Unicode code points", "single header and split headers", "custom number, string, array,", "and object types", ]), ("Coverage", ["push"], [ "unit tests cover all lines", "of the code base", ]), ("Static analysis", ["push", "sched"], [ "Clang-Tidy, Cppcheck, Infer", "Clang Static Analyzer (52", "checkers), IWYU, Codacy", "CodeQL, Semgrep, Flawfinder", "(also scheduled)", ]), ("Dynamic analysis", ["push"], [ "runtime assertions", "Valgrind (Memcheck)", "address and undefined behavior", "sanitizers, integer overflow,", "nullability violations", ]), ("Fuzzing", ["pr", "cont"], [ "CIFuzz on every pull request", "OSS-Fuzz: billions of inputs", "every crash fixed with a", "reproducing unit test", ]), ("Style", ["push"], [ "Artistic Style (astyle),", "enforced in the CI", "cpplint with 61 rules", ]), ("Docs and examples", ["push", "sched"], [ "every documentation example is", "tested; changed output fails", "site built with warnings", "as errors; links checked", "regularly (scheduled)", ]), ("Integration, supply chain", ["push", "sched"], [ "CMake 3.5, 3.31.6, and 4.0.0", "Meson, Swift Package Manager", "dependency review on every PR", "Dependabot daily, OpenSSF", "Scorecard, REUSE", ]), ] GLOSSARY = Path(__file__).resolve().parents[2] / "includes" / "glossary.md" ABBRS = sorted(re.findall(r"^\*\[([^\]]+)\]:", GLOSSARY.read_text(), re.M), key=len, reverse=True) def noabbr(s): """Breaks every glossary abbreviation with a zero-width space, so the abbr extension leaves SVG text alone.""" for a in ABBRS: s = re.sub(r"(?' % P, '' % (P, x, y, w, h), '%s' % (P, x + 14, y + 25, txt(title))] bx = x + 14 for t in triggers: label, mod = TRIGGERS[t] bw = badge_w(label) s.append('' % (P, P, mod, bx, y + 36, bw)) s.append('%s' % (P, P, mod, bx + bw / 2, y + 50, txt(label))) bx += bw + 6 for i, line in enumerate(lines): s.append('%s' % (P, x + 14, y + 78 + i * 18, txt(line))) s.append('') return "\n".join(s) def pill(cx, y, w, label, sub=None, cls=""): h = 46 if sub else 34 s = ['' % (P, cls, cx - w / 2, y, w, h, h / 2), '%s' % (P, cx, y + 22, txt(label))] if sub: s.append('%s' % (P, cx, y + 38, txt(sub))) return "\n".join(s), h def arrow(x, y1, y2, mid): return '' % (P, x, y1, x, y2, mid) def drawing(W, cols, suffix): mid = "nj-qa-arrow-" + suffix body = [] cx = W / 2 s, h = pill(cx, 8, 230, "push or pull request") body.append(s) y = 8 + h body.append(arrow(cx, y, y + 26, mid)) # container with the lanes top = y + 28 pad, gap = 14, 12 cw = (W - 24 - 2 * pad - (cols - 1) * gap) / cols body.append('checks, run in parallel on GitHub Actions and AppVeyor' % (P, 12 + pad, top + 24) if cols > 1 else 'checks, run in parallel' % (P, 12 + pad, top + 24)) gy = top + 40 rows = [LANES[i:i + cols] for i in range(0, len(LANES), cols)] for r in rows: ch = 78 + 18 * (max(len(l[2]) for l in r) - 1) + 16 for j, (title, trig, lines) in enumerate(r): body.append(card(12 + pad + j * (cw + gap), gy, cw, ch, title, trig, lines)) gy += ch + gap bottom = gy - gap + pad body.insert(0, '' % (P, top, W - 24, bottom - top)) # after the checks body.append(arrow(cx, bottom, bottom + 26, mid)) s, h = pill(cx, bottom + 28, 230, "merge into develop", cls=" %sstage--merge" % P) body.append(s) y = bottom + 28 + h body.append(arrow(cx, y, y + 26, mid)) s, h = pill(cx, y + 28, 300, "release", "no open vulnerability alert", cls=" %sstage--merge" % P) body.append(s) H = y + 28 + h + 8 defs = ('' '' % mid) tid, did = "nj-qa-title-" + suffix, "nj-qa-desc-" + suffix desc = ("Every push or pull request starts these checks in parallel: " + "; ".join("%s (%s): %s" % (t, ", ".join(TRIGGERS[k][0] for k in trig), " ".join(lines)) for t, trig, lines in LANES) + ". When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.") out = ['' % (P, P, "narrow" if cols == 1 else "wide", W, H, tid, did), 'Quality assurance pipeline' % tid, '%s' % (did, txt(desc)), defs] + body + [''] return "\n".join(out) def main(): (OUT / "qa_pipeline.svg").write_text(drawing(760, 3, "w") + "\n" + drawing(360, 1, "n") + "\n") main()