blob: df97b68f09fb409daad0563ce71e88ed44b8b9c7 [file] [log] [blame]
Spandan Dasaacf2372022-05-11 21:46:29 +00001#!/usr/bin/env python
2#
3# Copyright (C) 2022 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from abc import ABC, abstractmethod
18
19from collections.abc import Iterator
20from typing import List
21
22TAB = " "
23
24class Node(ABC):
25 '''An abstract class that can be serialized to a ninja file
26 All other ninja-serializable classes inherit from this class'''
27
28 @abstractmethod
29 def stream(self) -> Iterator[str]:
30 pass
31
32class Variable(Node):
33 '''A ninja variable that can be reused across build actions
34 https://ninja-build.org/manual.html#_variables'''
35
36 def __init__(self, name:str, value:str, indent=0):
37 self.name = name
38 self.value = value
39 self.indent = indent
40
41 def stream(self) -> Iterator[str]:
42 indent = TAB * self.indent
43 yield f"{indent}{self.name} = {self.value}"
44
45class RuleException(Exception):
46 pass
47
48# Ninja rules recognize a limited set of variables
49# https://ninja-build.org/manual.html#ref_rule
50# Keep this list sorted
51RULE_VARIABLES = ["command",
52 "depfile",
53 "deps",
54 "description",
55 "dyndep",
56 "generator",
57 "msvc_deps_prefix",
58 "restat",
59 "rspfile",
60 "rspfile_content"]
61
62class Rule(Node):
63 '''A shorthand for a command line that can be reused
64 https://ninja-build.org/manual.html#_rules'''
65
66 def __init__(self, name:str):
67 self.name = name
68 self.variables = []
69
70 def add_variable(self, name: str, value: str):
71 if name not in RULE_VARIABLES:
72 raise RuleException(f"{name} is not a recognized variable in a ninja rule")
73
74 self.variables.append(Variable(name=name, value=value, indent=1))
75
76 def stream(self) -> Iterator[str]:
77 self._validate_rule()
78
79 yield f"rule {self.name}"
80 # Yield rule variables sorted by `name`
81 for var in sorted(self.variables, key=lambda x: x.name):
82 # variables yield a single item, next() is sufficient
83 yield next(var.stream())
84
85 def _validate_rule(self):
86 # command is a required variable in a ninja rule
87 self._assert_variable_is_not_empty(variable_name="command")
88
89 def _assert_variable_is_not_empty(self, variable_name: str):
90 if not any(var.name == variable_name for var in self.variables):
91 raise RuleException(f"{variable_name} is required in a ninja rule")
92
93class BuildActionException(Exception):
94 pass
95
96class BuildAction(Node):
97 '''Describes the dependency edge between inputs and output
98 https://ninja-build.org/manual.html#_build_statements'''
99
100 def __init__(self, output: str, rule: str, inputs: List[str]=None, implicits: List[str]=None, order_only: List[str]=None):
101 self.output = output
102 self.rule = rule
103 self.inputs = self._as_list(inputs)
104 self.implicits = self._as_list(implicits)
105 self.order_only = self._as_list(order_only)
106 self.variables = []
107
108 def add_variable(self, name: str, value: str):
109 '''Variables limited to the scope of this build action'''
110 self.variables.append(Variable(name=name, value=value, indent=1))
111
112 def stream(self) -> Iterator[str]:
113 self._validate()
114
115 build_statement = f"build {self.output}: {self.rule}"
116 if len(self.inputs) > 0:
117 build_statement += " "
118 build_statement += " ".join(self.inputs)
119 if len(self.implicits) > 0:
120 build_statement += " | "
121 build_statement += " ".join(self.implicits)
122 if len(self.order_only) > 0:
123 build_statement += " || "
124 build_statement += " ".join(self.order_only)
125 yield build_statement
126 # Yield variables sorted by `name`
127 for var in sorted(self.variables, key=lambda x: x.name):
128 # variables yield a single item, next() is sufficient
129 yield next(var.stream())
130
131 def _validate(self):
132 if not self.output:
133 raise BuildActionException("Output is required in a ninja build statement")
134 if not self.rule:
135 raise BuildActionException("Rule is required in a ninja build statement")
136
137 def _as_list(self, list_like):
138 if list_like is None:
139 return []
140 if isinstance(list_like, list):
141 return list_like
142 return [list_like]
143
144class Pool(Node):
145 '''https://ninja-build.org/manual.html#ref_pool'''
146
147 def __init__(self, name: str, depth: int):
148 self.name = name
149 self.depth = Variable(name="depth", value=depth, indent=1)
150
151 def stream(self) -> Iterator[str]:
152 yield f"pool {self.name}"
153 yield next(self.depth.stream())
154
155class Subninja(Node):
156
157 def __init__(self, subninja: str, chDir: str):
158 self.subninja = subninja
159 self.chDir = chDir
160
161 # TODO(spandandas): Update the syntax when aosp/2064612 lands
Joe Onorato51497182022-05-12 12:58:10 -0700162 def stream(self) -> Iterator[str]:
Spandan Dasaacf2372022-05-11 21:46:29 +0000163 yield f"subninja {self.subninja}"
164
165class Line(Node):
166 '''Generic class that can be used for comments/newlines/default_target etc'''
167
168 def __init__(self, value:str):
169 self.value = value
170
171 def stream(self) -> Iterator[str]:
172 yield self.value