• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""Outputs the contents of blobs as a hard-coded arrays in a C++ library."""
15
16import argparse
17import itertools
18import json
19from pathlib import Path
20from string import Template
21import textwrap
22from typing import Any, Generator, Iterable, NamedTuple, Optional, Tuple
23
24HEADER_PREFIX = textwrap.dedent("""\
25    // This file is auto-generated; Do not hand-modify!
26    // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
27
28    #pragma once
29
30    #include <array>
31    #include <cstddef>
32    """)
33
34SOURCE_PREFIX_TEMPLATE = Template(
35    textwrap.dedent("""\
36        // This file is auto-generated; Do not hand-modify!
37        // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
38
39        #include "$header_path"
40
41        #include <array>
42        #include <cstddef>
43
44        #include "pw_preprocessor/compiler.h"
45        """))
46
47NAMESPACE_OPEN_TEMPLATE = Template('namespace ${namespace} {\n')
48
49NAMESPACE_CLOSE_TEMPLATE = Template('}  // namespace ${namespace}\n')
50
51BLOB_DECLARATION_TEMPLATE = Template(
52    'extern const std::array<std::byte, ${size_bytes}> ${symbol_name};')
53
54LINKER_SECTION_TEMPLATE = Template('PW_PLACE_IN_SECTION("${linker_section}")')
55
56BLOB_DEFINITION_SINGLE_LINE = Template(
57    'const std::array<std::byte, ${size_bytes}> ${symbol_name}'
58    ' = { $bytes };')
59
60BLOB_DEFINITION_MULTI_LINE = Template(
61    'const std::array<std::byte, ${size_bytes}> ${symbol_name}'
62    ' = {\n${bytes_lines}\n};')
63
64BYTES_PER_LINE = 4
65
66
67class Blob(NamedTuple):
68    symbol_name: str
69    file_path: Path
70    linker_section: Optional[str]
71
72
73def parse_args():
74    parser = argparse.ArgumentParser(description=__doc__)
75    parser.add_argument('--blob-file',
76                        type=Path,
77                        required=True,
78                        help=('Path to json file containing the list of blobs '
79                              'to generate.'))
80    parser.add_argument('--out-source',
81                        type=Path,
82                        required=True,
83                        help='Path at which to write .cpp file')
84    parser.add_argument('--out-header',
85                        type=Path,
86                        required=True,
87                        help='Path at which to write .h file')
88    parser.add_argument('--namespace',
89                        type=str,
90                        required=False,
91                        help=('Optional namespace that blobs will be scoped'
92                              'within.'))
93
94    return parser.parse_args()
95
96
97def split_into_chunks(
98        data: Iterable[Any],
99        chunk_size: int) -> Generator[Tuple[Any, ...], None, None]:
100    """Splits an iterable into chunks of a given size."""
101    data_iterator = iter(data)
102    chunk = tuple(itertools.islice(data_iterator, chunk_size))
103    while chunk:
104        yield chunk
105        chunk = tuple(itertools.islice(data_iterator, chunk_size))
106
107
108def header_from_blobs(blobs: Iterable[Blob],
109                      namespace: Optional[str] = None) -> str:
110    """Generate the contents of a C++ header file from blobs."""
111    lines = [HEADER_PREFIX]
112    if namespace:
113        lines.append(NAMESPACE_OPEN_TEMPLATE.substitute(namespace=namespace))
114    for blob in blobs:
115        data = blob.file_path.read_bytes()
116        lines.append(
117            BLOB_DECLARATION_TEMPLATE.substitute(symbol_name=blob.symbol_name,
118                                                 size_bytes=len(data)))
119        lines.append('')
120    if namespace:
121        lines.append(NAMESPACE_CLOSE_TEMPLATE.substitute(namespace=namespace))
122
123    return '\n'.join(lines)
124
125
126def array_def_from_blob_data(symbol_name: str, blob_data: bytes) -> str:
127    """Generates an array definition for the given blob data."""
128    byte_strs = ['std::byte{{0x{:02X}}}'.format(b) for b in blob_data]
129
130    # Try to fit the blob definition on a single line first.
131    single_line_def = BLOB_DEFINITION_SINGLE_LINE.substitute(
132        symbol_name=symbol_name,
133        size_bytes=len(blob_data),
134        bytes=', '.join(byte_strs))
135    if len(single_line_def) <= 80:
136        return single_line_def
137
138    # Blob definition didn't fit on a single line; do multi-line.
139    lines = []
140    for byte_strs_for_line in split_into_chunks(byte_strs, BYTES_PER_LINE):
141        bytes_segment = ', '.join(byte_strs_for_line)
142        lines.append(f'  {bytes_segment},')
143    # Removing the trailing comma from the final line of bytes.
144    lines[-1] = lines[-1].rstrip(',')
145
146    return BLOB_DEFINITION_MULTI_LINE.substitute(symbol_name=symbol_name,
147                                                 size_bytes=len(blob_data),
148                                                 bytes_lines='\n'.join(lines))
149
150
151def source_from_blobs(blobs: Iterable[Blob],
152                      header_path: Path,
153                      namespace: Optional[str] = None) -> str:
154    """Generate the contents of a C++ source file from blobs."""
155    lines = [SOURCE_PREFIX_TEMPLATE.substitute(header_path=header_path)]
156    if namespace:
157        lines.append(NAMESPACE_OPEN_TEMPLATE.substitute(namespace=namespace))
158    for blob in blobs:
159        if blob.linker_section:
160            lines.append(
161                LINKER_SECTION_TEMPLATE.substitute(
162                    linker_section=blob.linker_section))
163        data = blob.file_path.read_bytes()
164        lines.append(array_def_from_blob_data(blob.symbol_name, data))
165        lines.append('')
166    if namespace:
167        lines.append(NAMESPACE_CLOSE_TEMPLATE.substitute(namespace=namespace))
168
169    return '\n'.join(lines)
170
171
172def load_blobs(blob_file: Path) -> Iterable[Blob]:
173    with blob_file.open() as blob_fp:
174        return [
175            Blob(b["symbol_name"], Path(b["file_path"]),
176                 b.get("linker_section")) for b in json.load(blob_fp)
177        ]
178
179
180def main(blob_file: Path,
181         out_source: Path,
182         out_header: Path,
183         namespace: Optional[str] = None) -> None:
184    blobs = load_blobs(blob_file)
185    out_header.write_text(header_from_blobs(blobs, namespace))
186    out_source.write_text(source_from_blobs(blobs, out_header, namespace))
187
188
189if __name__ == '__main__':
190    main(**vars(parse_args()))
191