ninja_writer/ninja.rs
1//! Implementation of top-level stuff
2
3use alloc::boxed::Box;
4use core::fmt::{Display, Formatter, Result};
5
6use crate::stmt::{Stmt, StmtRef};
7use crate::util::{AddOnlyVec, RefCounted};
8use crate::{Build, BuildRef, Pool, PoolRef, Rule, RuleRef, ToArg, Variable};
9
10/// The main entry point for writing a ninja file.
11///
12/// # Examples
13/// See the [crate-level documentation](crate)
14#[derive(Debug)]
15pub struct Ninja {
16 /// The list of statements
17 pub stmts: RefCounted<AddOnlyVec<RefCounted<Stmt>>>,
18
19 /// The built-in phony rule,
20 pub phony: Rule,
21}
22
23impl Default for Ninja {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl Ninja {
30 /// Create a blank ninja file
31 pub fn new() -> Self {
32 Self {
33 phony: Rule::new("phony", ""),
34 stmts: Default::default(),
35 }
36 }
37
38 /// Create a new rule with the given name and command and add it to this ninja file.
39 ///
40 /// The returned [`RuleRef`] can be used to configure the rule and build edges
41 ///
42 /// # Example
43 /// ```rust
44 /// use ninja_writer::*;
45 ///
46 /// let ninja = Ninja::new();
47 /// let rule = ninja.rule("cc", "gcc -c $in -o $out");
48 /// rule.build(["foo.o"]).with(["foo.c"]);
49 ///
50 /// assert_eq!(ninja.to_string(), r###"
51 /// rule cc
52 /// command = gcc -c $in -o $out
53 ///
54 /// build foo.o: cc foo.c
55 /// "###);
56 #[inline]
57 pub fn rule(&self, name: impl ToArg, command: impl ToArg) -> RuleRef {
58 Rule::new(name, command).add_to(self)
59 }
60
61 /// Add a new build edge with the `phony` rule, used for aliasing
62 ///
63 /// See <https://ninja-build.org/manual.html#_the_literal_phony_literal_rule>
64 ///
65 /// # Example
66 /// ```rust
67 /// use ninja_writer::*;
68 ///
69 /// let ninja = Ninja::new();
70 /// ninja.phony(["all"]).with(["foo.o", "bar.o"]);
71 ///
72 /// assert_eq!(ninja.to_string(), r###"
73 /// build all: phony foo.o bar.o
74 /// "###);
75 /// ```
76 pub fn phony(&self, outputs: impl IntoIterator<Item = impl ToArg>) -> BuildRef {
77 let build = Build::new(&self.phony, outputs);
78 BuildRef(self.add_stmt(Stmt::Build(Box::new(build))))
79 }
80
81 /// Create a new [`Pool`] with the name and depth and add it to this ninja file.
82 /// Returns a reference of the pool for configuration, and for adding rules and builds to the
83 /// pool.
84 #[inline]
85 pub fn pool(&self, name: impl ToArg, depth: usize) -> PoolRef {
86 Pool::new(name, depth).add_to(self)
87 }
88
89 /// Add a comment
90 ///
91 /// # Example
92 /// ```rust
93 /// use ninja_writer::Ninja;
94 ///
95 /// let mut ninja = Ninja::new();
96 /// ninja.comment("This is a comment");
97 ///
98 /// assert_eq!(ninja.to_string(), r###"
99 /// ## This is a comment
100 /// "###);
101 /// ```
102 pub fn comment(&self, comment: impl ToArg) -> &Self {
103 self.stmts.add_rc(Stmt::Comment(comment.to_arg()));
104 self
105 }
106
107 /// Add a top-level variable
108 ///
109 /// # Example
110 /// ```rust
111 /// use ninja_writer::Ninja;
112 ///
113 /// let mut ninja = Ninja::new();
114 /// ninja.variable("foo", "bar");
115 /// ninja.variable("baz", "qux $bar");
116 ///
117 /// assert_eq!(ninja.to_string(), r###"
118 /// foo = bar
119 /// baz = qux $bar
120 /// "###);
121 /// ```
122 pub fn variable(&self, name: impl ToArg, value: impl ToArg) -> &Self {
123 self.stmts
124 .add_rc(Stmt::Variable(Variable::new(name, value)));
125 self
126 }
127
128 /// Add a default statement
129 ///
130 /// See <https://ninja-build.org/manual.html#_default_target_statements>
131 ///
132 /// # Example
133 /// ```rust
134 /// use ninja_writer::*;
135 ///
136 /// let ninja = Ninja::new();
137 /// ninja.defaults(["foo", "bar"]);
138 /// ninja.defaults(["baz"]);
139 ///
140 /// assert_eq!(ninja.to_string(), r###"
141 /// default foo bar
142 /// default baz
143 /// "###);
144 /// ```
145 pub fn defaults(&self, outputs: impl IntoIterator<Item = impl ToArg>) -> &Self {
146 self.stmts.add_rc(Stmt::Default(
147 outputs.into_iter().map(|s| s.to_arg()).collect(),
148 ));
149 self
150 }
151
152 /// Add a subninja statement
153 ///
154 /// See <https://ninja-build.org/manual.html#ref_scope>
155 /// # Example
156 /// ```rust
157 /// use ninja_writer::Ninja;
158 ///
159 /// let mut ninja = Ninja::new();
160 /// ninja.subninja("foo.ninja");
161 ///
162 /// assert_eq!(ninja.to_string(), r###"
163 /// subninja foo.ninja
164 /// "###);
165 /// ```
166 pub fn subninja(&self, path: impl ToArg) -> &Self {
167 self.stmts.add_rc(Stmt::Subninja(path.to_arg()));
168 self
169 }
170
171 /// Add an include statement.
172 ///
173 /// The difference between `include` and [`subninja`](Self::subninja) is that
174 /// `include` brings the variables into the current scope, much like `#include` in C.
175 ///
176 /// See <https://ninja-build.org/manual.html#ref_scope>
177 /// # Example
178 /// ```rust
179 /// use ninja_writer::Ninja;
180 ///
181 /// let mut ninja = Ninja::new();
182 /// ninja.include("foo.ninja");
183 /// ninja.include("bar.ninja");
184 ///
185 /// assert_eq!(ninja.to_string(), r###"
186 /// include foo.ninja
187 /// include bar.ninja
188 /// "###);
189 /// ```
190 pub fn include(&self, path: impl ToArg) -> &Self {
191 self.stmts.add_rc(Stmt::Include(path.to_arg()));
192 self
193 }
194
195 /// Internal function to add a statement
196 pub(crate) fn add_stmt(&self, stmt: Stmt) -> StmtRef {
197 StmtRef {
198 stmt: self.stmts.add_rc(stmt),
199 list: RefCounted::clone(&self.stmts),
200 }
201 }
202}
203
204impl Display for Ninja {
205 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
206 let list = &self.stmts.inner();
207 if list.is_empty() {
208 return Ok(());
209 }
210 let mut last = 0;
211 for stmt in list.iter() {
212 let stmt = stmt.as_ref();
213 // have a blank line between statement types and between rules
214 let next = stmt.ordinal() + 1;
215 if matches!(stmt, Stmt::Rule(_)) || next != last {
216 writeln!(f)?;
217 }
218 last = next;
219
220 match stmt {
221 Stmt::Rule(rule) => rule.fmt(f)?,
222 Stmt::Build(build) => build.fmt(f)?,
223 Stmt::Pool(pool) => pool.fmt(f)?,
224 Stmt::Comment(comment) => writeln!(f, "# {}", comment)?,
225 Stmt::Variable(variable) => {
226 variable.fmt(f)?;
227 writeln!(f)?;
228 }
229 Stmt::Default(outputs) => {
230 write!(f, "default")?;
231 for output in outputs {
232 write!(f, " {}", output)?;
233 }
234 writeln!(f)?;
235 }
236 Stmt::Subninja(path) => writeln!(f, "subninja {}", path)?,
237 Stmt::Include(path) => writeln!(f, "include {}", path)?,
238 }
239 }
240 Ok(())
241 }
242}
243
244#[cfg(test)]
245mod test {
246 use super::*;
247 use alloc::string::ToString;
248
249 #[test]
250 fn test_default() {
251 let ninja = Ninja::new();
252 assert_eq!(ninja.to_string(), "");
253 }
254
255 // doc tests should give enough coverage
256}