• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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