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