| #!/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/function.patch. |
| """ |
| |
| from pathlib import Path |
| import re |
| import subprocess |
| import tempfile |
| from typing import Iterable, List, TextIO, Optional, Union |
| from datetime import datetime |
| |
| PathOrStr = Union[Path, str] |
| |
| 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. |
| |
| # 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' |
| |
| |
| # TODO(b/248257406): Replace typing.List with list. # pylint: disable=fixme |
| def _read_files(script: Path) -> List[Path]: |
| with script.open() as file: |
| paths_list: List[str] = eval(''.join(_read_files_list(file))) # pylint: disable=eval-used |
| return list(Path(p) for p in paths_list if not 'lib/stdcompat/' 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') |
| |
| |
| _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: |
| if file.name != 'function_internal.h': |
| return text |
| |
| text = _add_include_before_namespace(text, 'pw_preprocessor/compiler.h') |
| return text.replace('\n Result invoke(Args... args) const {', |
| _INVOKE_PATCH) |
| |
| |
| def _patch(file: Path) -> Optional[str]: |
| text = file.read_text() |
| updated = _patch_assert(text) |
| updated = _patch_invoke(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() |