1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Common logic needed by other modules.""" 5 6import contextlib 7import dataclasses 8import filecmp 9import os 10import shutil 11import tempfile 12import pathlib 13import zipfile 14 15 16# Only some methods respect line length, so this is more of a best-effort 17# limit. 18_TARGET_LINE_LENGTH = 100 19 20 21@dataclasses.dataclass(frozen=True) 22class JniMode: 23 is_hashing: bool = False 24 is_muxing: bool = False 25 is_per_file: bool = False 26 27 28JniMode.MUXING = JniMode(is_muxing=True) 29 30 31class StringBuilder: 32 33 def __init__(self): 34 self._sb = [] 35 self._indent = 0 36 self._start_of_line = True 37 38 def __call__(self, value): 39 lines = value.splitlines(keepends=True) 40 for line in lines: 41 if self._start_of_line and line != '\n': 42 self._sb.append(' ' * self._indent) 43 self._sb.append(line) 44 self._start_of_line = line[-1] == '\n' 45 46 def _cur_line_length(self): 47 ret = 0 48 for l in reversed(self._sb): 49 if l.endswith('\n'): 50 break 51 ret += len(l) 52 return ret 53 54 @contextlib.contextmanager 55 def _param_list_generator(self): 56 values = [] 57 yield values 58 self.param_list(values) 59 60 def param_list(self, values=None): 61 if values is None: 62 return self._param_list_generator() 63 64 self('(') 65 if values: 66 punctuation_size = 2 * len(values) # punctuation: ", ()" 67 single_line_size = sum(len(v) for v in values) + punctuation_size 68 if self._cur_line_length() + single_line_size < _TARGET_LINE_LENGTH: 69 self(', '.join(values)) 70 else: 71 self('\n') 72 with self.indent(4): 73 self(',\n'.join(values)) 74 self(')') 75 76 def line(self, value=None): 77 self(value) 78 self('\n') 79 80 @contextlib.contextmanager 81 def statement(self): 82 yield 83 self(';\n') 84 85 @contextlib.contextmanager 86 def section(self, section_title): 87 if not ''.join(self._sb[-2:]).endswith('\n\n'): 88 self('\n') 89 self(f'// {section_title}\n') 90 yield 91 self('\n') 92 93 @contextlib.contextmanager 94 def namespace(self, namespace_name): 95 if namespace_name is None: 96 yield 97 return 98 value = f' {namespace_name}' if namespace_name else '' 99 self(f'namespace{value} {{\n\n') 100 yield 101 self(f'\n}} // namespace{value}\n') 102 103 @contextlib.contextmanager 104 def block(self, *, indent=2, after=None): 105 self(' {\n') 106 with self.indent(indent): 107 yield 108 if after: 109 self('}') 110 self(after) 111 self('\n') 112 else: 113 self('}\n') 114 115 @contextlib.contextmanager 116 def indent(self, amount): 117 self._indent += amount 118 yield 119 self._indent -= amount 120 121 def to_string(self): 122 return ''.join(self._sb) 123 124 125def capitalize(value): 126 return value[0].upper() + value[1:] 127 128 129def jni_mangle(name): 130 """Performs JNI mangling on the given name.""" 131 # https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp615 132 return name.replace('_', '_1').replace('/', '_').replace('$', '_00024') 133 134 135@contextlib.contextmanager 136def atomic_output(path, mode='w+b'): 137 with tempfile.NamedTemporaryFile(mode, delete=False) as f: 138 try: 139 yield f 140 finally: 141 f.close() 142 143 if not (os.path.exists(path) and filecmp.cmp(f.name, path)): 144 pathlib.Path(path).parents[0].mkdir(parents=True, exist_ok=True) 145 shutil.move(f.name, path) 146 if os.path.exists(f.name): 147 os.unlink(f.name) 148 149 150def add_to_zip_hermetic(zip_file, zip_path, data=None): 151 zipinfo = zipfile.ZipInfo(filename=zip_path) 152 zipinfo.external_attr = 0o644 << 16 153 zipinfo.date_time = (2001, 1, 1, 0, 0, 0) 154 zip_file.writestr(zipinfo, data, zipfile.ZIP_STORED) 155 156 157def should_rename_package(package_name, filter_list_string): 158 # If the filter list is empty, all packages should be renamed. 159 if not filter_list_string: 160 return True 161 162 return any( 163 package_name.startswith(pkg_prefix) 164 for pkg_prefix in filter_list_string.split(':')) 165