xref: /aosp_15_r20/external/pigweed/third_party/nanopb/generate_nanopb_proto.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Generates nanopb_pb2.py by importing the Nanopb proto module.
15
16The Nanopb repository generates nanopb_pb2.py dynamically when its Python
17package is imported if it does not exist. If multiple processes try to use
18Nanopb to compile simultaneously on a clean build, they can interfere with each
19other. One process might rewrite nanopb_pb2.py as another process is trying to
20access it, resulting in import errors.
21
22This script imports the Nanopb module so that nanopb_pb2.py is generated if it
23doesn't exist. All Nanopb proto compilation targets depend on this script so
24that nanopb_pb2.py is guaranteed to exist before they need it.
25"""
26
27import argparse
28import importlib.util
29from pathlib import Path
30import os
31import sys
32
33
34def generate_nanopb_proto(
35    nanopb_root: Path, protoc_binary: Path, aggressive_regen: bool
36) -> None:
37    generated_nanopb_pb2 = nanopb_root / 'generator' / 'proto' / 'nanopb_pb2.py'
38
39    # If protoc was updated, ensure the file is regenerated.
40    if generated_nanopb_pb2.is_file():
41        if (
42            aggressive_regen
43            or protoc_binary.stat().st_mtime
44            > generated_nanopb_pb2.stat().st_mtime
45        ):
46            os.remove(generated_nanopb_pb2)
47
48    sys.path.insert(0, str(nanopb_root / 'generator'))
49
50    spec = importlib.util.spec_from_file_location(
51        'proto', nanopb_root / 'generator' / 'nanopb_generator.py'
52    )
53    assert spec is not None
54    proto_module = importlib.util.module_from_spec(spec)
55    spec.loader.exec_module(proto_module)  # type: ignore[union-attr]
56    assert generated_nanopb_pb2.is_file(), 'Could not generate nanopb_pb2.py'
57
58
59def _parse_args() -> argparse.Namespace:
60    parser = argparse.ArgumentParser(description=__doc__)
61    parser.add_argument(
62        '--nanopb-root',
63        type=Path,
64        help='Nanopb root',
65    )
66    parser.add_argument(
67        '--protoc-binary',
68        type=Path,
69        help='Protoc binary path',
70    )
71    parser.add_argument(
72        '--aggressive-regen',
73        action="store_true",
74        default=False,
75        help=(
76            'If true, always regenerates nanopb_pb2.py when this script is '
77            'run. If this is false, the file is only regenerated when the '
78            'protoc binary is updated'
79        ),
80    )
81    return parser.parse_args()
82
83
84if __name__ == '__main__':
85    generate_nanopb_proto(**vars(_parse_args()))
86