1695b41eeSopenharmony_ci#!/usr/bin/python 2695b41eeSopenharmony_ci 3695b41eeSopenharmony_ci# Copyright 2011 Google Inc. All Rights Reserved. 4695b41eeSopenharmony_ci# 5695b41eeSopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License"); 6695b41eeSopenharmony_ci# you may not use this file except in compliance with the License. 7695b41eeSopenharmony_ci# You may obtain a copy of the License at 8695b41eeSopenharmony_ci# 9695b41eeSopenharmony_ci# http://www.apache.org/licenses/LICENSE-2.0 10695b41eeSopenharmony_ci# 11695b41eeSopenharmony_ci# Unless required by applicable law or agreed to in writing, software 12695b41eeSopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS, 13695b41eeSopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14695b41eeSopenharmony_ci# See the License for the specific language governing permissions and 15695b41eeSopenharmony_ci# limitations under the License. 16695b41eeSopenharmony_ci 17695b41eeSopenharmony_ci"""Python module for generating .ninja files. 18695b41eeSopenharmony_ci 19695b41eeSopenharmony_ciNote that this is emphatically not a required piece of Ninja; it's 20695b41eeSopenharmony_cijust a helpful utility for build-file-generation systems that already 21695b41eeSopenharmony_ciuse Python. 22695b41eeSopenharmony_ci""" 23695b41eeSopenharmony_ci 24695b41eeSopenharmony_ciimport re 25695b41eeSopenharmony_ciimport textwrap 26695b41eeSopenharmony_cifrom io import TextIOWrapper 27695b41eeSopenharmony_cifrom typing import Dict, List, Match, Optional, Tuple, Union 28695b41eeSopenharmony_ci 29695b41eeSopenharmony_cidef escape_path(word: str) -> str: 30695b41eeSopenharmony_ci return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') 31695b41eeSopenharmony_ci 32695b41eeSopenharmony_ciclass Writer(object): 33695b41eeSopenharmony_ci def __init__(self, output: TextIOWrapper, width: int = 78) -> None: 34695b41eeSopenharmony_ci self.output = output 35695b41eeSopenharmony_ci self.width = width 36695b41eeSopenharmony_ci 37695b41eeSopenharmony_ci def newline(self) -> None: 38695b41eeSopenharmony_ci self.output.write('\n') 39695b41eeSopenharmony_ci 40695b41eeSopenharmony_ci def comment(self, text: str) -> None: 41695b41eeSopenharmony_ci for line in textwrap.wrap(text, self.width - 2, break_long_words=False, 42695b41eeSopenharmony_ci break_on_hyphens=False): 43695b41eeSopenharmony_ci self.output.write('# ' + line + '\n') 44695b41eeSopenharmony_ci 45695b41eeSopenharmony_ci def variable( 46695b41eeSopenharmony_ci self, 47695b41eeSopenharmony_ci key: str, 48695b41eeSopenharmony_ci value: Optional[Union[bool, int, float, str, List[str]]], 49695b41eeSopenharmony_ci indent: int = 0, 50695b41eeSopenharmony_ci ) -> None: 51695b41eeSopenharmony_ci if value is None: 52695b41eeSopenharmony_ci return 53695b41eeSopenharmony_ci if isinstance(value, list): 54695b41eeSopenharmony_ci value = ' '.join(filter(None, value)) # Filter out empty strings. 55695b41eeSopenharmony_ci self._line('%s = %s' % (key, value), indent) 56695b41eeSopenharmony_ci 57695b41eeSopenharmony_ci def pool(self, name: str, depth: int) -> None: 58695b41eeSopenharmony_ci self._line('pool %s' % name) 59695b41eeSopenharmony_ci self.variable('depth', depth, indent=1) 60695b41eeSopenharmony_ci 61695b41eeSopenharmony_ci def rule( 62695b41eeSopenharmony_ci self, 63695b41eeSopenharmony_ci name: str, 64695b41eeSopenharmony_ci command: str, 65695b41eeSopenharmony_ci description: Optional[str] = None, 66695b41eeSopenharmony_ci depfile: Optional[str] = None, 67695b41eeSopenharmony_ci generator: bool = False, 68695b41eeSopenharmony_ci pool: Optional[str] = None, 69695b41eeSopenharmony_ci restat: bool = False, 70695b41eeSopenharmony_ci rspfile: Optional[str] = None, 71695b41eeSopenharmony_ci rspfile_content: Optional[str] = None, 72695b41eeSopenharmony_ci deps: Optional[Union[str, List[str]]] = None, 73695b41eeSopenharmony_ci ) -> None: 74695b41eeSopenharmony_ci self._line('rule %s' % name) 75695b41eeSopenharmony_ci self.variable('command', command, indent=1) 76695b41eeSopenharmony_ci if description: 77695b41eeSopenharmony_ci self.variable('description', description, indent=1) 78695b41eeSopenharmony_ci if depfile: 79695b41eeSopenharmony_ci self.variable('depfile', depfile, indent=1) 80695b41eeSopenharmony_ci if generator: 81695b41eeSopenharmony_ci self.variable('generator', '1', indent=1) 82695b41eeSopenharmony_ci if pool: 83695b41eeSopenharmony_ci self.variable('pool', pool, indent=1) 84695b41eeSopenharmony_ci if restat: 85695b41eeSopenharmony_ci self.variable('restat', '1', indent=1) 86695b41eeSopenharmony_ci if rspfile: 87695b41eeSopenharmony_ci self.variable('rspfile', rspfile, indent=1) 88695b41eeSopenharmony_ci if rspfile_content: 89695b41eeSopenharmony_ci self.variable('rspfile_content', rspfile_content, indent=1) 90695b41eeSopenharmony_ci if deps: 91695b41eeSopenharmony_ci self.variable('deps', deps, indent=1) 92695b41eeSopenharmony_ci 93695b41eeSopenharmony_ci def build( 94695b41eeSopenharmony_ci self, 95695b41eeSopenharmony_ci outputs: Union[str, List[str]], 96695b41eeSopenharmony_ci rule: str, 97695b41eeSopenharmony_ci inputs: Optional[Union[str, List[str]]] = None, 98695b41eeSopenharmony_ci implicit: Optional[Union[str, List[str]]] = None, 99695b41eeSopenharmony_ci order_only: Optional[Union[str, List[str]]] = None, 100695b41eeSopenharmony_ci variables: Optional[ 101695b41eeSopenharmony_ci Union[ 102695b41eeSopenharmony_ci List[Tuple[str, Optional[Union[str, List[str]]]]], 103695b41eeSopenharmony_ci Dict[str, Optional[Union[str, List[str]]]], 104695b41eeSopenharmony_ci ] 105695b41eeSopenharmony_ci ] = None, 106695b41eeSopenharmony_ci implicit_outputs: Optional[Union[str, List[str]]] = None, 107695b41eeSopenharmony_ci pool: Optional[str] = None, 108695b41eeSopenharmony_ci dyndep: Optional[str] = None, 109695b41eeSopenharmony_ci ) -> List[str]: 110695b41eeSopenharmony_ci outputs = as_list(outputs) 111695b41eeSopenharmony_ci out_outputs = [escape_path(x) for x in outputs] 112695b41eeSopenharmony_ci all_inputs = [escape_path(x) for x in as_list(inputs)] 113695b41eeSopenharmony_ci 114695b41eeSopenharmony_ci if implicit: 115695b41eeSopenharmony_ci implicit = [escape_path(x) for x in as_list(implicit)] 116695b41eeSopenharmony_ci all_inputs.append('|') 117695b41eeSopenharmony_ci all_inputs.extend(implicit) 118695b41eeSopenharmony_ci if order_only: 119695b41eeSopenharmony_ci order_only = [escape_path(x) for x in as_list(order_only)] 120695b41eeSopenharmony_ci all_inputs.append('||') 121695b41eeSopenharmony_ci all_inputs.extend(order_only) 122695b41eeSopenharmony_ci if implicit_outputs: 123695b41eeSopenharmony_ci implicit_outputs = [escape_path(x) 124695b41eeSopenharmony_ci for x in as_list(implicit_outputs)] 125695b41eeSopenharmony_ci out_outputs.append('|') 126695b41eeSopenharmony_ci out_outputs.extend(implicit_outputs) 127695b41eeSopenharmony_ci 128695b41eeSopenharmony_ci self._line('build %s: %s' % (' '.join(out_outputs), 129695b41eeSopenharmony_ci ' '.join([rule] + all_inputs))) 130695b41eeSopenharmony_ci if pool is not None: 131695b41eeSopenharmony_ci self._line(' pool = %s' % pool) 132695b41eeSopenharmony_ci if dyndep is not None: 133695b41eeSopenharmony_ci self._line(' dyndep = %s' % dyndep) 134695b41eeSopenharmony_ci 135695b41eeSopenharmony_ci if variables: 136695b41eeSopenharmony_ci if isinstance(variables, dict): 137695b41eeSopenharmony_ci iterator = iter(variables.items()) 138695b41eeSopenharmony_ci else: 139695b41eeSopenharmony_ci iterator = iter(variables) 140695b41eeSopenharmony_ci 141695b41eeSopenharmony_ci for key, val in iterator: 142695b41eeSopenharmony_ci self.variable(key, val, indent=1) 143695b41eeSopenharmony_ci 144695b41eeSopenharmony_ci return outputs 145695b41eeSopenharmony_ci 146695b41eeSopenharmony_ci def include(self, path: str) -> None: 147695b41eeSopenharmony_ci self._line('include %s' % path) 148695b41eeSopenharmony_ci 149695b41eeSopenharmony_ci def subninja(self, path: str) -> None: 150695b41eeSopenharmony_ci self._line('subninja %s' % path) 151695b41eeSopenharmony_ci 152695b41eeSopenharmony_ci def default(self, paths: Union[str, List[str]]) -> None: 153695b41eeSopenharmony_ci self._line('default %s' % ' '.join(as_list(paths))) 154695b41eeSopenharmony_ci 155695b41eeSopenharmony_ci def _count_dollars_before_index(self, s: str, i: int) -> int: 156695b41eeSopenharmony_ci """Returns the number of '$' characters right in front of s[i].""" 157695b41eeSopenharmony_ci dollar_count = 0 158695b41eeSopenharmony_ci dollar_index = i - 1 159695b41eeSopenharmony_ci while dollar_index > 0 and s[dollar_index] == '$': 160695b41eeSopenharmony_ci dollar_count += 1 161695b41eeSopenharmony_ci dollar_index -= 1 162695b41eeSopenharmony_ci return dollar_count 163695b41eeSopenharmony_ci 164695b41eeSopenharmony_ci def _line(self, text: str, indent: int = 0) -> None: 165695b41eeSopenharmony_ci """Write 'text' word-wrapped at self.width characters.""" 166695b41eeSopenharmony_ci leading_space = ' ' * indent 167695b41eeSopenharmony_ci while len(leading_space) + len(text) > self.width: 168695b41eeSopenharmony_ci # The text is too wide; wrap if possible. 169695b41eeSopenharmony_ci 170695b41eeSopenharmony_ci # Find the rightmost space that would obey our width constraint and 171695b41eeSopenharmony_ci # that's not an escaped space. 172695b41eeSopenharmony_ci available_space = self.width - len(leading_space) - len(' $') 173695b41eeSopenharmony_ci space = available_space 174695b41eeSopenharmony_ci while True: 175695b41eeSopenharmony_ci space = text.rfind(' ', 0, space) 176695b41eeSopenharmony_ci if (space < 0 or 177695b41eeSopenharmony_ci self._count_dollars_before_index(text, space) % 2 == 0): 178695b41eeSopenharmony_ci break 179695b41eeSopenharmony_ci 180695b41eeSopenharmony_ci if space < 0: 181695b41eeSopenharmony_ci # No such space; just use the first unescaped space we can find. 182695b41eeSopenharmony_ci space = available_space - 1 183695b41eeSopenharmony_ci while True: 184695b41eeSopenharmony_ci space = text.find(' ', space + 1) 185695b41eeSopenharmony_ci if (space < 0 or 186695b41eeSopenharmony_ci self._count_dollars_before_index(text, space) % 2 == 0): 187695b41eeSopenharmony_ci break 188695b41eeSopenharmony_ci if space < 0: 189695b41eeSopenharmony_ci # Give up on breaking. 190695b41eeSopenharmony_ci break 191695b41eeSopenharmony_ci 192695b41eeSopenharmony_ci self.output.write(leading_space + text[0:space] + ' $\n') 193695b41eeSopenharmony_ci text = text[space+1:] 194695b41eeSopenharmony_ci 195695b41eeSopenharmony_ci # Subsequent lines are continuations, so indent them. 196695b41eeSopenharmony_ci leading_space = ' ' * (indent+2) 197695b41eeSopenharmony_ci 198695b41eeSopenharmony_ci self.output.write(leading_space + text + '\n') 199695b41eeSopenharmony_ci 200695b41eeSopenharmony_ci def close(self) -> None: 201695b41eeSopenharmony_ci self.output.close() 202695b41eeSopenharmony_ci 203695b41eeSopenharmony_ci 204695b41eeSopenharmony_cidef as_list(input: Optional[Union[str, List[str]]]) -> List[str]: 205695b41eeSopenharmony_ci if input is None: 206695b41eeSopenharmony_ci return [] 207695b41eeSopenharmony_ci if isinstance(input, list): 208695b41eeSopenharmony_ci return input 209695b41eeSopenharmony_ci return [input] 210695b41eeSopenharmony_ci 211695b41eeSopenharmony_ci 212695b41eeSopenharmony_cidef escape(string: str) -> str: 213695b41eeSopenharmony_ci """Escape a string such that it can be embedded into a Ninja file without 214695b41eeSopenharmony_ci further interpretation.""" 215695b41eeSopenharmony_ci assert '\n' not in string, 'Ninja syntax does not allow newlines' 216695b41eeSopenharmony_ci # We only have one special metacharacter: '$'. 217695b41eeSopenharmony_ci return string.replace('$', '$$') 218695b41eeSopenharmony_ci 219695b41eeSopenharmony_ci 220695b41eeSopenharmony_cidef expand(string: str, vars: Dict[str, str], local_vars: Dict[str, str] = {}) -> str: 221695b41eeSopenharmony_ci """Expand a string containing $vars as Ninja would. 222695b41eeSopenharmony_ci 223695b41eeSopenharmony_ci Note: doesn't handle the full Ninja variable syntax, but it's enough 224695b41eeSopenharmony_ci to make configure.py's use of it work. 225695b41eeSopenharmony_ci """ 226695b41eeSopenharmony_ci def exp(m: Match[str]) -> str: 227695b41eeSopenharmony_ci var = m.group(1) 228695b41eeSopenharmony_ci if var == '$': 229695b41eeSopenharmony_ci return '$' 230695b41eeSopenharmony_ci return local_vars.get(var, vars.get(var, '')) 231695b41eeSopenharmony_ci return re.sub(r'\$(\$|\w*)', exp, string) 232