#!/usr/bin/env python # Copyright 2022 The Pigweed Authors # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. """Generates a patch file for sources from Fuchsia's fit and stdcompat. Run this script to update third_party/fuchsia/pigweed_adaptations.patch. """ from pathlib import Path import re import subprocess import tempfile from typing import Iterable, TextIO from datetime import datetime HEADER = f'''# Copyright {datetime.today().year} The Pigweed Authors # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. # DO NOT EDIT! This file is generated by {Path(__file__).name}. # # To make changes, update and run ./{Path(__file__).name}. # Patch the fit::function implementation for use in Pigweed: # # - Use PW_ASSERT instead of __builtin_abort. # - Temporarily disable sanitizers when invoking a function for b/241567321. # '''.encode() def _read_files_list(file: TextIO) -> Iterable[str]: """Reads the files list from the copy.bara.sky file.""" found_list = False for line in file: if found_list: yield line if line == ']\n': break else: if line == 'fuchsia_repo_files = [\n': found_list = True yield '[' def _clone_fuchsia(temp_path: Path) -> Path: subprocess.run( [ 'git', '-C', temp_path, 'clone', '--depth', '1', 'https://fuchsia.googlesource.com/fuchsia', ], check=True, ) return temp_path / 'fuchsia' def _read_files(script: Path) -> list[Path]: with script.open() as file: paths_list: list[str] = eval( # pylint: disable=eval-used ''.join(_read_files_list(file)) ) return list(Path(p) for p in paths_list if 'lib/stdcompat/' not in p) def _add_include_before_namespace(text: str, include: str) -> str: return text.replace( '\nnamespace ', f'\n#include "{include}"\n\nnamespace ', 1 ) _ASSERT = re.compile(r'\bassert\(') def _patch_assert(text: str) -> str: replaced = text.replace('__builtin_abort()', 'PW_ASSERT(false)') replaced = _ASSERT.sub('PW_ASSERT(', replaced) if replaced == text: return replaced return _add_include_before_namespace(replaced, 'pw_assert/assert.h') _CONSTINIT = re.compile(r'\b__CONSTINIT\b') def _patch_constinit(text: str) -> str: replaced = _CONSTINIT.sub('PW_CONSTINIT', text) if replaced == text: return text replaced = replaced.replace('#include \n', '') return _add_include_before_namespace( replaced, "pw_polyfill/language_feature_macros.h" ) _INVOKE_PATCH = ( '\n' ' // TODO: b/241567321 - Remove "no sanitize" after pw_protobuf is fixed.\n' ' Result invoke(Args... args) const PW_NO_SANITIZE("function") {' ) def _patch_invoke(file: Path, text: str) -> str: # Update internal/function.h only. if file.name != 'function.h' or file.parent.name != 'internal': return text text = _add_include_before_namespace(text, 'pw_preprocessor/compiler.h') return text.replace( '\n Result invoke(Args... args) const {', _INVOKE_PATCH ) def _ignore_errors(file: Path, text: str) -> str: # Only patch result.h for now if file.name != 'result.h': return text text = _add_include_before_namespace(text, 'pw_preprocessor/compiler.h') # Push the diagnostics before the namespace. new_lines = ['', 'PW_MODIFY_DIAGNOSTICS_PUSH();'] for clang_diag in ['-Wshadow-field-in-constructor']: new_lines.append( f'PW_MODIFY_DIAGNOSTIC_CLANG(ignored, "{clang_diag}");' ) new_lines.append('') new_lines.append('') text = text.replace('\nnamespace ', '\n'.join(new_lines) + 'namespace ', 1) # Pop the diagnostics before the include guard's #endif split_text = text.rsplit('\n#endif', 1) text = '\nPW_MODIFY_DIAGNOSTICS_POP();\n\n#endif'.join(split_text) return text def _patch(file: Path) -> str | None: text = file.read_text() updated = _patch_assert(text) updated = _patch_constinit(updated) updated = _patch_invoke(file, updated) updated = _ignore_errors(file, updated) return None if text == updated else updated def _main() -> None: output_path = Path(__file__).parent # Clone Fuchsia to a temp directory with tempfile.TemporaryDirectory() as directory: repo = _clone_fuchsia(Path(directory)) # Read the files list from copy.bara.sky and patch those files. paths = _read_files(output_path / 'copy.bara.sky') for file in (repo / path for path in paths): if (text := _patch(file)) is not None: print('Patching', file) file.write_text(text) subprocess.run(['clang-format', '-i', file], check=True) # Create a diff for the changes. diff = subprocess.run( ['git', '-C', repo, 'diff'], stdout=subprocess.PIPE, check=True ).stdout for path in paths: diff = diff.replace( path.as_posix().encode(), Path('third_party/fuchsia/repo', path).as_posix().encode(), ) # Write the diff to function.patch. with output_path.joinpath('pigweed_adaptations.patch').open('wb') as output: output.write(HEADER) for line in diff.splitlines(keepends=True): if line == b' \n': output.write(b'\n') elif not line.startswith(b'index '): # drop line with Git hashes output.write(line) if __name__ == '__main__': _main()