xref: /aosp_15_r20/external/pigweed/third_party/fuchsia/generate_fuchsia_patch.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1#!/usr/bin/env python
2# Copyright 2022 The Pigweed Authors
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may not
5# use this file except in compliance with the License. You may obtain a copy of
6# the License at
7#
8#     https://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations under
14# the License.
15"""Generates a patch file for sources from Fuchsia's fit and stdcompat.
16
17Run this script to update third_party/fuchsia/pigweed_adaptations.patch.
18"""
19
20from pathlib import Path
21import re
22import subprocess
23import tempfile
24from typing import Iterable, TextIO
25from datetime import datetime
26
27HEADER = f'''# Copyright {datetime.today().year} The Pigweed Authors
28#
29# Licensed under the Apache License, Version 2.0 (the "License"); you may not
30# use this file except in compliance with the License. You may obtain a copy of
31# the License at
32#
33#     https://www.apache.org/licenses/LICENSE-2.0
34#
35# Unless required by applicable law or agreed to in writing, software
36# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
37# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
38# License for the specific language governing permissions and limitations under
39# the License.
40
41# DO NOT EDIT! This file is generated by {Path(__file__).name}.
42#
43# To make changes, update and run ./{Path(__file__).name}.
44
45# Patch the fit::function implementation for use in Pigweed:
46#
47#   - Use PW_ASSERT instead of __builtin_abort.
48#   - Temporarily disable sanitizers when invoking a function for b/241567321.
49#
50'''.encode()
51
52
53def _read_files_list(file: TextIO) -> Iterable[str]:
54    """Reads the files list from the copy.bara.sky file."""
55    found_list = False
56
57    for line in file:
58        if found_list:
59            yield line
60            if line == ']\n':
61                break
62        else:
63            if line == 'fuchsia_repo_files = [\n':
64                found_list = True
65                yield '['
66
67
68def _clone_fuchsia(temp_path: Path) -> Path:
69    subprocess.run(
70        [
71            'git',
72            '-C',
73            temp_path,
74            'clone',
75            '--depth',
76            '1',
77            'https://fuchsia.googlesource.com/fuchsia',
78        ],
79        check=True,
80    )
81
82    return temp_path / 'fuchsia'
83
84
85def _read_files(script: Path) -> list[Path]:
86    with script.open() as file:
87        paths_list: list[str] = eval(  # pylint: disable=eval-used
88            ''.join(_read_files_list(file))
89        )
90        return list(Path(p) for p in paths_list if 'lib/stdcompat/' not in p)
91
92
93def _add_include_before_namespace(text: str, include: str) -> str:
94    return text.replace(
95        '\nnamespace ', f'\n#include "{include}"\n\nnamespace ', 1
96    )
97
98
99_ASSERT = re.compile(r'\bassert\(')
100
101
102def _patch_assert(text: str) -> str:
103    replaced = text.replace('__builtin_abort()', 'PW_ASSERT(false)')
104    replaced = _ASSERT.sub('PW_ASSERT(', replaced)
105
106    if replaced == text:
107        return replaced
108
109    return _add_include_before_namespace(replaced, 'pw_assert/assert.h')
110
111
112_CONSTINIT = re.compile(r'\b__CONSTINIT\b')
113
114
115def _patch_constinit(text: str) -> str:
116    replaced = _CONSTINIT.sub('PW_CONSTINIT', text)
117
118    if replaced == text:
119        return text
120
121    replaced = replaced.replace('#include <zircon/compiler.h>\n', '')
122    return _add_include_before_namespace(
123        replaced, "pw_polyfill/language_feature_macros.h"
124    )
125
126
127_INVOKE_PATCH = (
128    '\n'
129    '  // TODO: b/241567321 - Remove "no sanitize" after pw_protobuf is fixed.\n'
130    '  Result invoke(Args... args) const PW_NO_SANITIZE("function") {'
131)
132
133
134def _patch_invoke(file: Path, text: str) -> str:
135    # Update internal/function.h only.
136    if file.name != 'function.h' or file.parent.name != 'internal':
137        return text
138
139    text = _add_include_before_namespace(text, 'pw_preprocessor/compiler.h')
140    return text.replace(
141        '\n  Result invoke(Args... args) const {', _INVOKE_PATCH
142    )
143
144
145def _ignore_errors(file: Path, text: str) -> str:
146    # Only patch result.h for now
147    if file.name != 'result.h':
148        return text
149
150    text = _add_include_before_namespace(text, 'pw_preprocessor/compiler.h')
151
152    # Push the diagnostics before the namespace.
153    new_lines = ['', 'PW_MODIFY_DIAGNOSTICS_PUSH();']
154    for clang_diag in ['-Wshadow-field-in-constructor']:
155        new_lines.append(
156            f'PW_MODIFY_DIAGNOSTIC_CLANG(ignored, "{clang_diag}");'
157        )
158    new_lines.append('')
159    new_lines.append('')
160
161    text = text.replace('\nnamespace ', '\n'.join(new_lines) + 'namespace ', 1)
162
163    # Pop the diagnostics before the include guard's #endif
164    split_text = text.rsplit('\n#endif', 1)
165    text = '\nPW_MODIFY_DIAGNOSTICS_POP();\n\n#endif'.join(split_text)
166    return text
167
168
169def _patch(file: Path) -> str | None:
170    text = file.read_text()
171    updated = _patch_assert(text)
172    updated = _patch_constinit(updated)
173    updated = _patch_invoke(file, updated)
174    updated = _ignore_errors(file, updated)
175    return None if text == updated else updated
176
177
178def _main() -> None:
179    output_path = Path(__file__).parent
180
181    # Clone Fuchsia to a temp directory
182    with tempfile.TemporaryDirectory() as directory:
183        repo = _clone_fuchsia(Path(directory))
184
185        # Read the files list from copy.bara.sky and patch those files.
186        paths = _read_files(output_path / 'copy.bara.sky')
187        for file in (repo / path for path in paths):
188            if (text := _patch(file)) is not None:
189                print('Patching', file)
190                file.write_text(text)
191                subprocess.run(['clang-format', '-i', file], check=True)
192
193        # Create a diff for the changes.
194        diff = subprocess.run(
195            ['git', '-C', repo, 'diff'], stdout=subprocess.PIPE, check=True
196        ).stdout
197        for path in paths:
198            diff = diff.replace(
199                path.as_posix().encode(),
200                Path('third_party/fuchsia/repo', path).as_posix().encode(),
201            )
202
203    # Write the diff to function.patch.
204    with output_path.joinpath('pigweed_adaptations.patch').open('wb') as output:
205        output.write(HEADER)
206
207        for line in diff.splitlines(keepends=True):
208            if line == b' \n':
209                output.write(b'\n')
210            elif not line.startswith(b'index '):  # drop line with Git hashes
211                output.write(line)
212
213
214if __name__ == '__main__':
215    _main()
216