• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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