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}