cabin_build/graph.rs
1use cabin_core::StandardFlagConflict;
2use camino::Utf8PathBuf;
3
4use cabin_driver::{BuildAction, Dialect};
5
6use crate::standard_compat::StandardCompatViolation;
7
8/// Backend-independent description of everything that needs to happen to
9/// realize a build. A backend (currently `cabin-ninja`) walks this graph,
10/// lowers each semantic [`BuildAction`] to a concrete command via
11/// [`cabin_driver::lower()`] for the graph's [`Dialect`], and emits the
12/// equivalent build-system-specific representation.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct BuildGraph {
15 /// All actions to execute, in topological order. Earlier actions in the
16 /// vector never depend on later actions. These are *semantic*
17 /// actions: compile / archive / link intent, not pre-lowered argv.
18 pub actions: Vec<BuildAction>,
19 /// Command-line dialect every action in this graph is lowered for.
20 /// The backend reads it to pick compile/archive/link spellings and
21 /// the matching Ninja dependency-tracking mode.
22 pub dialect: Dialect,
23 /// Output paths that should be marked as default targets.
24 pub default_outputs: Vec<Utf8PathBuf>,
25 /// One entry per C/C++ source compile, used to emit
26 /// `compile_commands.json`. Both languages contribute entries
27 /// with their language-appropriate compiler driver and flags
28 /// recorded in `arguments`.
29 pub compile_commands: Vec<CompileCommand>,
30 /// Standards problems recorded against *planned* compiles. The
31 /// planner records these instead of failing eagerly: the
32 /// `cabin check` rewrite prunes dependency compiles after
33 /// planning, and a violation that does not survive into the
34 /// final graph must not gate the command. The CLI surfaces
35 /// survivors through [`crate::validate_planned_standards`]
36 /// before anything is lowered or written.
37 pub standard_violations: Vec<StandardViolation>,
38 /// Violated dependency edges from the post-resolution
39 /// standard-compatibility pass ([`crate::standard_compat`]).
40 /// Always empty unless the pass ran
41 /// ([`crate::PlanRequest::standard_compat`]). The CLI renders
42 /// non-ignored records as errors that fail the command and
43 /// `ignored` records as unchecked-edge notes; the `cabin check`
44 /// rewrite carries them through unchanged (they describe
45 /// resolved edges, not planned compiles).
46 pub standard_compat_violations: Vec<StandardCompatViolation>,
47}
48
49/// One standards problem recorded against a planned compile. Each
50/// variant carries the offending compile's object path so the
51/// `cabin check` rewrite can prune violations with the same path
52/// filter as the compiles they belong to.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum StandardViolation {
55 /// An MSVC-dialect compile whose standard `cl.exe` has no
56 /// stable `/std:` flag - the planner cannot lower it (its
57 /// compile-commands entry is omitted).
58 MsvcSpelling {
59 /// `package:target` of the offending compile.
60 target: String,
61 /// Human label of the source language (`C` / `C++`).
62 language: &'static str,
63 /// Canonical spelling of the offending standard.
64 standard: &'static str,
65 /// Object path of the offending compile.
66 object: Utf8PathBuf,
67 },
68 /// An MSVC-dialect compile whose target enables
69 /// `gnu-extensions` - `cl.exe` has no GNU dialect mode, so the
70 /// planner cannot honor the flag and must not silently ignore
71 /// it (its compile-commands entry is omitted).
72 MsvcGnuExtensions {
73 /// `package:target` of the offending compile.
74 target: String,
75 /// Object path of the offending compile.
76 object: Utf8PathBuf,
77 },
78 /// A compile that carries both a first-class standard
79 /// declaration and an explicit `-std=` / `/std:` token in its
80 /// manifest-derived flag list - the documented escape-hatch
81 /// ambiguity, scoped to compiles the declaration covers.
82 FlagConflict {
83 conflict: StandardFlagConflict,
84 /// Object path of the offending compile.
85 object: Utf8PathBuf,
86 },
87 /// A consuming compile whose effective implementation standard
88 /// is below a reachable library-like dependency's interface
89 /// requirement for the same language. Recorded against the
90 /// *consumer's* compile so the `cabin check` rewrite prunes the
91 /// incompatibility together with the compiles it protects - a
92 /// dependency-internal incompatibility never gates a check that
93 /// only compiles the selected packages' own translation units.
94 InterfaceIncompatibility {
95 consumer: String,
96 dependency: String,
97 language: &'static str,
98 consumer_standard: &'static str,
99 required: &'static str,
100 requirement_source: &'static str,
101 /// Object path of one of the consumer's compiles of the
102 /// language (every object of a target shares the same
103 /// per-package prefix the check filter tests).
104 object: Utf8PathBuf,
105 },
106}
107
108impl StandardViolation {
109 /// Object path of the offending compile, for the check
110 /// rewrite's path filter.
111 #[must_use]
112 pub fn object(&self) -> &Utf8PathBuf {
113 match self {
114 Self::MsvcSpelling { object, .. }
115 | Self::MsvcGnuExtensions { object, .. }
116 | Self::FlagConflict { object, .. }
117 | Self::InterfaceIncompatibility { object, .. } => object,
118 }
119 }
120}
121
122/// One entry of a Clang JSON Compilation Database.
123///
124/// `arguments` is kept as a list so each backend / consumer can render it
125/// however the format requires (LLVM accepts both `command` and
126/// `arguments` keys; `cabin-ninja` emits `command`).
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct CompileCommand {
129 pub directory: Utf8PathBuf,
130 pub file: Utf8PathBuf,
131 pub arguments: Vec<String>,
132 pub output: Utf8PathBuf,
133}