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/function.patch. 18""" 19 20from pathlib import Path 21import re 22import subprocess 23import tempfile 24from typing import Iterable, List, TextIO, Optional, Union 25from datetime import datetime 26 27PathOrStr = Union[Path, str] 28 29HEADER = f'''# Copyright {datetime.today().year} The Pigweed Authors 30# 31# Licensed under the Apache License, Version 2.0 (the "License"); you may not 32# use this file except in compliance with the License. You may obtain a copy of 33# the License at 34# 35# https://www.apache.org/licenses/LICENSE-2.0 36# 37# Unless required by applicable law or agreed to in writing, software 38# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 39# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 40# License for the specific language governing permissions and limitations under 41# the License. 42 43# Patch the fit::function implementation for use in Pigweed: 44# 45# - Use PW_ASSERT instead of __builtin_abort. 46# - Temporarily disable sanitizers when invoking a function for b/241567321. 47# 48'''.encode() 49 50 51def _read_files_list(file: TextIO) -> Iterable[str]: 52 """Reads the files list from the copy.bara.sky file.""" 53 found_list = False 54 55 for line in file: 56 if found_list: 57 yield line 58 if line == ']\n': 59 break 60 else: 61 if line == 'fuchsia_repo_files = [\n': 62 found_list = True 63 yield '[' 64 65 66def _clone_fuchsia(temp_path: Path) -> Path: 67 subprocess.run( 68 [ 69 'git', 70 '-C', 71 temp_path, 72 'clone', 73 '--depth', 74 '1', 75 'https://fuchsia.googlesource.com/fuchsia', 76 ], 77 check=True, 78 ) 79 80 return temp_path / 'fuchsia' 81 82 83# TODO(b/248257406): Replace typing.List with list. # pylint: disable=fixme 84def _read_files(script: Path) -> List[Path]: 85 with script.open() as file: 86 paths_list: List[str] = eval( # pylint: disable=eval-used 87 ''.join(_read_files_list(file)) 88 ) 89 return list(Path(p) for p in paths_list if not 'lib/stdcompat/' in p) 90 91 92def _add_include_before_namespace(text: str, include: str) -> str: 93 return text.replace( 94 '\nnamespace ', f'\n#include "{include}"\n\nnamespace ', 1 95 ) 96 97 98_ASSERT = re.compile(r'\bassert\(') 99 100 101def _patch_assert(text: str) -> str: 102 replaced = text.replace('__builtin_abort()', 'PW_ASSERT(false)') 103 replaced = _ASSERT.sub('PW_ASSERT(', replaced) 104 105 if replaced == text: 106 return replaced 107 108 return _add_include_before_namespace(replaced, 'pw_assert/assert.h') 109 110 111_INVOKE_PATCH = ( 112 '\n' 113 ' // TODO(b/241567321): Remove "no sanitize" after pw_protobuf is fixed.\n' 114 ' Result invoke(Args... args) const PW_NO_SANITIZE("function") {' 115) 116 117 118def _patch_invoke(file: Path, text: str) -> str: 119 # Update internal/function.h only. 120 if file.name != 'function.h' or file.parent.name != 'internal': 121 return text 122 123 text = _add_include_before_namespace(text, 'pw_preprocessor/compiler.h') 124 return text.replace( 125 '\n Result invoke(Args... args) const {', _INVOKE_PATCH 126 ) 127 128 129def _patch(file: Path) -> Optional[str]: 130 text = file.read_text() 131 updated = _patch_assert(text) 132 updated = _patch_invoke(file, updated) 133 return None if text == updated else updated 134 135 136def _main() -> None: 137 output_path = Path(__file__).parent 138 139 # Clone Fuchsia to a temp directory 140 with tempfile.TemporaryDirectory() as directory: 141 repo = _clone_fuchsia(Path(directory)) 142 143 # Read the files list from copy.bara.sky and patch those files. 144 paths = _read_files(output_path / 'copy.bara.sky') 145 for file in (repo / path for path in paths): 146 if (text := _patch(file)) is not None: 147 print('Patching', file) 148 file.write_text(text) 149 subprocess.run(['clang-format', '-i', file], check=True) 150 151 # Create a diff for the changes. 152 diff = subprocess.run( 153 ['git', '-C', repo, 'diff'], stdout=subprocess.PIPE, check=True 154 ).stdout 155 for path in paths: 156 diff = diff.replace( 157 path.as_posix().encode(), 158 Path('third_party/fuchsia/repo', path).as_posix().encode(), 159 ) 160 161 # Write the diff to function.patch. 162 with output_path.joinpath('pigweed_adaptations.patch').open('wb') as output: 163 output.write(HEADER) 164 165 for line in diff.splitlines(keepends=True): 166 if line == b' \n': 167 output.write(b'\n') 168 elif not line.startswith(b'index '): # drop line with Git hashes 169 output.write(line) 170 171 172if __name__ == '__main__': 173 _main() 174