From 867ddb838101dc3f9d930e0af403d474e921cbef Mon Sep 17 00:00:00 2001 From: Wyatt Hepler Date: Fri, 8 Jul 2022 15:37:56 +0000 Subject: [PATCH] pw_build: Updates to pw_cc_blob_library - Support specifying the alignment of the blob with the alignas argument. - Require the namespace argument. - Isolate blob include paths under "$target_gen_dir/$target_name" so they aren't shared by all blobs declared in the same directory. - No longer use different one-line formatting for short blobs to simplify code generation. - Add a unit test that checks the contents of a blob. Change-Id: I61d58b6cb95576a89fd8123b9f41ffeb0db38860 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/100801 Pigweed-Auto-Submit: Wyatt Hepler Reviewed-by: Ewout van Bekkum Commit-Queue: Auto-Submit --- pw_build/BUILD.bazel | 7 + pw_build/BUILD.gn | 28 +++ pw_build/cc_blob_library.gni | 46 ++-- pw_build/cc_blob_library_test.cc | 53 ++++ pw_build/docs.rst | 7 +- pw_build/generated_pigweed_modules_lists.gni | 1 + pw_build/py/generate_cc_blob_library_test.py | 232 +++++++++--------- .../py/pw_build/generate_cc_blob_library.py | 114 +++++---- pw_build/test_blob_0123.bin | Bin 0 -> 4 bytes .../py/pw_presubmit/pigweed_presubmit.py | 1 + 10 files changed, 294 insertions(+), 195 deletions(-) create mode 100644 pw_build/cc_blob_library_test.cc create mode 100644 pw_build/test_blob_0123.bin diff --git a/pw_build/BUILD.bazel b/pw_build/BUILD.bazel index 276122752..49e59cdbb 100644 --- a/pw_build/BUILD.bazel +++ b/pw_build/BUILD.bazel @@ -22,3 +22,10 @@ config_setting( "define": "kythe_corpus=pigweed.googlesource.com/pigweed/pigweed", }, ) + +# TODO(b/238339027): Run cc_blob_library_test when pw_cc_blob_library is +# supported in Bazel. +filegroup( + name = "cc_blob_library_test", + srcs = ["cc_blob_library_test.cc"], +) diff --git a/pw_build/BUILD.gn b/pw_build/BUILD.gn index bf92666fb..823088289 100644 --- a/pw_build/BUILD.gn +++ b/pw_build/BUILD.gn @@ -14,10 +14,12 @@ import("//build_overrides/pigweed.gni") +import("$dir_pw_build/cc_blob_library.gni") import("$dir_pw_build/python.gni") import("$dir_pw_build/relative_source_file_names.gni") import("$dir_pw_docgen/docs.gni") import("$dir_pw_toolchain/traits.gni") +import("$dir_pw_unit_test/test.gni") import("target_types.gni") # IMPORTANT: The compilation flags in this file must be kept in sync with @@ -231,3 +233,29 @@ pw_doc_group("docs") { "python.rst", ] } + +pw_test("cc_blob_library_test") { + sources = [ "cc_blob_library_test.cc" ] + deps = [ ":test_blob" ] +} + +pw_cc_blob_library("test_blob") { + out_header = "pw_build/test_blob.h" + namespace = "test::ns" + blobs = [ + { + file_path = "test_blob_0123.bin" + symbol_name = "kFirstBlob0123" + alignas = 512 + }, + { + file_path = "test_blob_0123.bin" + symbol_name = "kSecondBlob0123" + }, + ] + visibility = [ ":*" ] +} + +pw_test_group("tests") { + tests = [ ":cc_blob_library_test" ] +} diff --git a/pw_build/cc_blob_library.gni b/pw_build/cc_blob_library.gni index 7e794d996..259f27383 100644 --- a/pw_build/cc_blob_library.gni +++ b/pw_build/cc_blob_library.gni @@ -18,6 +18,8 @@ import("$dir_pw_build/python_action.gni") import("$dir_pw_build/target_types.gni") # Turns binary blobs into a C++ source_set library of hard-coded byte arrays. +# The byte arrays are constant initialized and are safe to access at any time, +# including before main(). # # blobs A list of scopes, where each scope corresponds to a binary # blob to be transformed from file to byte array. This is a @@ -30,15 +32,21 @@ import("$dir_pw_build/target_types.gni") # linker_section [optional]: If present, places the byte array # in the specified linker section. # +# alignas [optional]: If present, the byte array is aligned as +# specified. The value of this argument is used verbatim +# in an alignas() specifier for the blob byte array. +# # out_header The header file to generate. Users will include this file # exactly as it is written here to reference the byte arrays. # -# namespace An optional (but highly recommended!) C++ namespace to place -# the generated blobs within. +# namespace The C++ namespace in which to place the generated blobs. +# template("pw_cc_blob_library") { assert(defined(invoker.blobs), "pw_cc_blob_library requires 'blobs'") assert(defined(invoker.out_header), "pw_cc_blob_library requires an 'out_header'") + assert(defined(invoker.namespace), + "pw_cc_blob_library requires a 'namespace'") _blobs = [] _blob_files = [] @@ -50,9 +58,13 @@ template("pw_cc_blob_library") { _blob_files += [ blob.file_path ] } - _blob_json_file = "$target_gen_dir/$target_name.json" + _out_dir = "$target_gen_dir/$target_name" + _blob_json_file = "$_out_dir/blobs.json" write_file(_blob_json_file, _blobs, "json") + _header = "$_out_dir/public/${invoker.out_header}" + _source = "$_out_dir/" + get_path_info(invoker.out_header, "name") + ".cc" + pw_python_action("$target_name._gen") { forward_variables_from(invoker, [ @@ -62,25 +74,17 @@ template("pw_cc_blob_library") { module = "pw_build.generate_cc_blob_library" python_deps = [ "$dir_pw_build/py" ] - _header = "${target_gen_dir}/public/" + invoker.out_header - _source = - "${target_gen_dir}/" + get_path_info(invoker.out_header, "name") + ".cc" args = [ "--blob-file", rebase_path(_blob_json_file, root_build_dir), + "--namespace=${invoker.namespace}", + "--header-include=${invoker.out_header}", "--out-header", - rebase_path(_header), + rebase_path(_header, root_build_dir), "--out-source", - rebase_path(_source), + rebase_path(_source, root_build_dir), ] - if (defined(invoker.namespace)) { - args += [ - "--namespace", - invoker.namespace, - ] - } - inputs = [ _blob_json_file ] + _blob_files outputs = [ _header, @@ -89,14 +93,18 @@ template("pw_cc_blob_library") { } config("$target_name._include_path") { - include_dirs = [ "${target_gen_dir}/public" ] + include_dirs = [ "$_out_dir/public" ] visibility = [ ":*" ] } pw_source_set(target_name) { - sources = get_target_outputs(":$target_name._gen") + public = [ _header ] + sources = [ _source ] public_configs = [ ":$target_name._include_path" ] - deps = [ ":$target_name._gen" ] - public_deps = [ "$dir_pw_preprocessor" ] + deps = [ + ":$target_name._gen", + dir_pw_preprocessor, + ] + forward_variables_from(invoker, [ "visibility" ]) } } diff --git a/pw_build/cc_blob_library_test.cc b/pw_build/cc_blob_library_test.cc new file mode 100644 index 000000000..91db07ad1 --- /dev/null +++ b/pw_build/cc_blob_library_test.cc @@ -0,0 +1,53 @@ +// 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. + +#include + +#include "gtest/gtest.h" +#include "pw_build/test_blob.h" + +namespace pw::build { +namespace { + +static_assert(test::ns::kFirstBlob0123.size() == 4); +static_assert(test::ns::kFirstBlob0123.data() != nullptr); + +static_assert(test::ns::kSecondBlob0123.size() == 4); +static_assert(test::ns::kSecondBlob0123.data() != nullptr); + +TEST(CcBlobLibraryTest, FirstBlobContentsMatch) { + EXPECT_EQ(test::ns::kFirstBlob0123[0], std::byte{0}); + EXPECT_EQ(test::ns::kFirstBlob0123[1], std::byte{1}); + EXPECT_EQ(test::ns::kFirstBlob0123[2], std::byte{2}); + EXPECT_EQ(test::ns::kFirstBlob0123[3], std::byte{3}); +} + +TEST(CcBlobLibraryTest, SecondBlobContentsMatch) { + EXPECT_EQ(test::ns::kSecondBlob0123[0], std::byte{0}); + EXPECT_EQ(test::ns::kSecondBlob0123[1], std::byte{1}); + EXPECT_EQ(test::ns::kSecondBlob0123[2], std::byte{2}); + EXPECT_EQ(test::ns::kSecondBlob0123[3], std::byte{3}); +} + +TEST(CcBlobLibraryTest, FirstBlobAlignedTo512) { + // This checks that the variable is aligned to 512, but cannot guarantee that + // alignas was specified correctly, since it could be aligned to 512 by + // coincidence. + const uintptr_t addr = reinterpret_cast(&test::ns::kFirstBlob0123); + constexpr uintptr_t kAlignmentMask = static_cast(512 - 1); + EXPECT_EQ(addr & kAlignmentMask, 0u); +} + +} // namespace +} // namespace pw::build diff --git a/pw_build/docs.rst b/pw_build/docs.rst index 3c10b0267..f88e1972f 100644 --- a/pw_build/docs.rst +++ b/pw_build/docs.rst @@ -128,7 +128,10 @@ pw_cc_blob_library The ``pw_cc_blob_library`` template is useful for embedding binary data into a program. The template takes in a mapping of symbol names to file paths, and generates a set of C++ source and header files that embed the contents of the -passed-in files as arrays. +passed-in files as arrays of ``std::byte``. + +The blob byte arrays are constant initialized and are safe to access at any +time, including before ``main()``. **Arguments** @@ -140,6 +143,8 @@ passed-in files as arrays. * ``file_path``: The file path for the binary blob. * ``linker_section``: If present, places the byte array in the specified linker section. + * ``alignas``: If present, uses the specified string or integer verbatim in + the ``alignas()`` specifier for the byte array. * ``out_header``: The header file to generate. Users will include this file exactly as it is written here to reference the byte arrays. diff --git a/pw_build/generated_pigweed_modules_lists.gni b/pw_build/generated_pigweed_modules_lists.gni index 750ab713b..b211c2e7d 100644 --- a/pw_build/generated_pigweed_modules_lists.gni +++ b/pw_build/generated_pigweed_modules_lists.gni @@ -297,6 +297,7 @@ declare_args() { "$dir_pw_blob_store:tests", "$dir_pw_bluetooth:tests", "$dir_pw_bluetooth_hci:tests", + "$dir_pw_build:tests", "$dir_pw_bytes:tests", "$dir_pw_checksum:tests", "$dir_pw_chrono:tests", diff --git a/pw_build/py/generate_cc_blob_library_test.py b/pw_build/py/generate_cc_blob_library_test.py index 2185f85cb..4bd178119 100644 --- a/pw_build/py/generate_cc_blob_library_test.py +++ b/pw_build/py/generate_cc_blob_library_test.py @@ -15,11 +15,53 @@ from pathlib import Path import tempfile -import textwrap import unittest from pw_build import generate_cc_blob_library +COMMENT = """\ +// This file was generated by generate_cc_blob_library.py. +// +// DO NOT EDIT! +// +// This file contains declarations for byte arrays created from files during the +// build. The byte arrays are constant initialized and are safe to access at any +// time, including before main(). +// +// See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. +""" + +COMMON_HEADER_START = COMMENT + """\ +#pragma once + +#include +#include +""" + +COMMON_SOURCE_START = COMMENT + """\ + +#include "path/to/header.h" + +#include +#include + +#include "pw_preprocessor/compiler.h" +""" + +FOO_BLOB = """\ +constexpr std::array fooBlob = { + std::byte{0x01}, std::byte{0x02}, +}; +""" + +BAR_BLOB = """\ +constexpr std::array barBlob = { + std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, + std::byte{0x05}, std::byte{0x06}, std::byte{0x07}, std::byte{0x08}, + std::byte{0x09}, std::byte{0x0A}, +}; +""" + class TestSplitIntoChunks(unittest.TestCase): """Unit tests for the split_into_chunks() function.""" @@ -47,18 +89,10 @@ def test_single_blob_header(self): blobs = [generate_cc_blob_library.Blob('fooBlob', foo_blob, None)] header = generate_cc_blob_library.header_from_blobs(blobs) - expected_header = textwrap.dedent("""\ - // This file is auto-generated; Do not hand-modify! - // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. - - #pragma once - - #include - #include - - extern const std::array fooBlob; - """) - + expected_header = ( + f'{COMMON_HEADER_START}' + '\n\n' # No namespace, so two blank lines + 'extern const std::array fooBlob;\n') self.assertEqual(expected_header, header) def test_multi_blob_header(self): @@ -73,19 +107,11 @@ def test_multi_blob_header(self): ] header = generate_cc_blob_library.header_from_blobs(blobs) - expected_header = textwrap.dedent("""\ - // This file is auto-generated; Do not hand-modify! - // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. - - #pragma once - - #include - #include - - extern const std::array fooBlob; - - extern const std::array barBlob; - """) + expected_header = (f'{COMMON_HEADER_START}\n' + '\n' + 'extern const std::array fooBlob;\n' + '\n' + 'extern const std::array barBlob;\n') self.assertEqual(expected_header, header) @@ -96,21 +122,13 @@ def test_header_with_namespace(self): blobs = [generate_cc_blob_library.Blob('fooBlob', foo_blob, None)] header = generate_cc_blob_library.header_from_blobs(blobs, 'pw::foo') - expected_header = textwrap.dedent("""\ - // This file is auto-generated; Do not hand-modify! - // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. - - #pragma once - - #include - #include - - namespace pw::foo { - - extern const std::array fooBlob; - - } // namespace pw::foo - """) + expected_header = (f'{COMMON_HEADER_START}' + '\n' + 'namespace pw::foo {\n' + '\n' + 'extern const std::array fooBlob;\n' + '\n' + '} // namespace pw::foo\n') self.assertEqual(expected_header, header) @@ -118,31 +136,20 @@ def test_header_with_namespace(self): class TestArrayDefFromBlobData(unittest.TestCase): """Unit tests for the array_def_from_blob_data() function.""" def test_single_line(self): - """Tests the generation of single-line array definitions.""" + """Tests the generation of array definitions with one line of data.""" foo_data = bytes((1, 2)) foo_definition = generate_cc_blob_library.array_def_from_blob_data( - 'fooBlob', foo_data) - expected_definition = ('const std::array fooBlob' - ' = { std::byte{0x01}, std::byte{0x02} };') - - self.assertEqual(expected_definition, foo_definition) + generate_cc_blob_library.Blob('fooBlob', Path(), None), foo_data) + self.assertEqual(f'\n{FOO_BLOB}', foo_definition) def test_multi_line(self): """Tests the generation of multi-line array definitions.""" - foo_data = bytes((1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) - - foo_definition = generate_cc_blob_library.array_def_from_blob_data( - 'fooBlob', foo_data) - expected_definition = ('const std::array fooBlob = {\n' - ' std::byte{0x01}, std::byte{0x02}, ' - 'std::byte{0x03}, std::byte{0x04},\n' - ' std::byte{0x05}, std::byte{0x06}, ' - 'std::byte{0x07}, std::byte{0x08},\n' - ' std::byte{0x09}, std::byte{0x0A}\n' - '};') + bar_data = bytes((1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) - self.assertEqual(expected_definition, foo_definition) + bar_definition = generate_cc_blob_library.array_def_from_blob_data( + generate_cc_blob_library.Blob('barBlob', Path(), None), bar_data) + self.assertEqual(f'\n{BAR_BLOB}', bar_definition) class TestSourceFromBlobs(unittest.TestCase): @@ -160,27 +167,12 @@ def test_source_with_mixed_blobs(self): source = generate_cc_blob_library.source_from_blobs( blobs, 'path/to/header.h') - expected_source = textwrap.dedent("""\ - // This file is auto-generated; Do not hand-modify! - // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. - - #include "path/to/header.h" - - #include - #include - - #include "pw_preprocessor/compiler.h" - - """) - expected_source += ('const std::array fooBlob' - ' = { std::byte{0x01}, std::byte{0x02} };\n\n') - expected_source += ('const std::array barBlob = {\n' - ' std::byte{0x01}, std::byte{0x02}, ' - 'std::byte{0x03}, std::byte{0x04},\n' - ' std::byte{0x05}, std::byte{0x06}, ' - 'std::byte{0x07}, std::byte{0x08},\n' - ' std::byte{0x09}, std::byte{0x0A}\n' - '};\n') + expected_source = (f'{COMMON_SOURCE_START}' + '\n' + '\n' + f'{FOO_BLOB}' + '\n' + f'{BAR_BLOB}') self.assertEqual(expected_source, source) @@ -192,62 +184,60 @@ def test_source_with_namespace(self): source = generate_cc_blob_library.source_from_blobs( blobs, 'path/to/header.h', 'pw::foo') - expected_source = textwrap.dedent("""\ - // This file is auto-generated; Do not hand-modify! - // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. - - #include "path/to/header.h" - - #include - #include + expected_source = (f'{COMMON_SOURCE_START}' + '\n' + 'namespace pw::foo {\n' + '\n' + f'{FOO_BLOB}' + '\n' + '} // namespace pw::foo\n') - #include "pw_preprocessor/compiler.h" - - namespace pw::foo { + self.assertEqual(expected_source, source) - const std::array fooBlob = { std::byte{0x01}, std::byte{0x02} }; + def test_source_with_linker_sections(self): + """Tests generation of a source file with defined linker sections.""" + foo_blob = Path(tempfile.NamedTemporaryFile().name) + foo_blob.write_bytes(bytes((1, 2))) + bar_blob = Path(tempfile.NamedTemporaryFile().name) + bar_blob.write_bytes(bytes((1, 2, 3, 4, 5, 6, 7, 8, 9, 10))) + blobs = [ + generate_cc_blob_library.Blob('fooBlob', foo_blob, '.foo_section'), + generate_cc_blob_library.Blob('barBlob', bar_blob, '.bar_section'), + ] - } // namespace pw::foo - """) + source = generate_cc_blob_library.source_from_blobs( + blobs, 'path/to/header.h') + expected_source = (f'{COMMON_SOURCE_START}' + '\n' + '\n' + 'PW_PLACE_IN_SECTION(".foo_section")\n' + f'{FOO_BLOB}' + '\n' + 'PW_PLACE_IN_SECTION(".bar_section")\n' + f'{BAR_BLOB}') self.assertEqual(expected_source, source) - def test_source_with_linker_sections(self): - """Tests generation of a source file with defined linker sections""" + def test_source_with_alignas(self): + """Tests generation of a source file with alignas specified.""" foo_blob = Path(tempfile.NamedTemporaryFile().name) foo_blob.write_bytes(bytes((1, 2))) bar_blob = Path(tempfile.NamedTemporaryFile().name) bar_blob.write_bytes(bytes((1, 2, 3, 4, 5, 6, 7, 8, 9, 10))) blobs = [ - generate_cc_blob_library.Blob('fooBlob', foo_blob, ".foo_section"), - generate_cc_blob_library.Blob('barBlob', bar_blob, ".bar_section"), + generate_cc_blob_library.Blob('fooBlob', foo_blob, None, '64'), + generate_cc_blob_library.Blob('barBlob', bar_blob, '.abc', 'int'), ] source = generate_cc_blob_library.source_from_blobs( blobs, 'path/to/header.h') - expected_source = textwrap.dedent("""\ - // This file is auto-generated; Do not hand-modify! - // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. - - #include "path/to/header.h" - - #include - #include - - #include "pw_preprocessor/compiler.h" - - """) - expected_source += ('PW_PLACE_IN_SECTION(".foo_section")\n' - 'const std::array fooBlob' - ' = { std::byte{0x01}, std::byte{0x02} };\n\n') - expected_source += ('PW_PLACE_IN_SECTION(".bar_section")\n' - 'const std::array barBlob = {\n' - ' std::byte{0x01}, std::byte{0x02}, ' - 'std::byte{0x03}, std::byte{0x04},\n' - ' std::byte{0x05}, std::byte{0x06}, ' - 'std::byte{0x07}, std::byte{0x08},\n' - ' std::byte{0x09}, std::byte{0x0A}\n' - '};\n') + expected_source = (f'{COMMON_SOURCE_START}' + '\n' + '\n' + f'alignas(64) {FOO_BLOB}' + '\n' + 'PW_PLACE_IN_SECTION(".abc")\n' + f'alignas(int) {BAR_BLOB}') self.assertEqual(expected_source, source) diff --git a/pw_build/py/pw_build/generate_cc_blob_library.py b/pw_build/py/pw_build/generate_cc_blob_library.py index 551701eb5..7f5b1ecff 100644 --- a/pw_build/py/pw_build/generate_cc_blob_library.py +++ b/pw_build/py/pw_build/generate_cc_blob_library.py @@ -19,22 +19,30 @@ from pathlib import Path from string import Template import textwrap -from typing import Any, Generator, Iterable, NamedTuple, Optional, Tuple - -HEADER_PREFIX = textwrap.dedent("""\ - // This file is auto-generated; Do not hand-modify! - // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. - +from typing import (Any, Generator, Iterable, NamedTuple, Optional, Sequence, + Tuple) + +COMMENT = f"""\ +// This file was generated by {Path(__file__).name}. +// +// DO NOT EDIT! +// +// This file contains declarations for byte arrays created from files during the +// build. The byte arrays are constant initialized and are safe to access at any +// time, including before main(). +// +// See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. +""" + +HEADER_PREFIX = COMMENT + textwrap.dedent("""\ #pragma once #include #include + """) -SOURCE_PREFIX_TEMPLATE = Template( - textwrap.dedent("""\ - // This file is auto-generated; Do not hand-modify! - // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details. +SOURCE_PREFIX_TEMPLATE = Template(COMMENT + textwrap.dedent("""\ #include "$header_path" @@ -42,24 +50,23 @@ #include #include "pw_preprocessor/compiler.h" + """)) NAMESPACE_OPEN_TEMPLATE = Template('namespace ${namespace} {\n') -NAMESPACE_CLOSE_TEMPLATE = Template('} // namespace ${namespace}\n') +NAMESPACE_CLOSE_TEMPLATE = Template('\n} // namespace ${namespace}\n') BLOB_DECLARATION_TEMPLATE = Template( - 'extern const std::array ${symbol_name};') - -LINKER_SECTION_TEMPLATE = Template('PW_PLACE_IN_SECTION("${linker_section}")') + '\nextern const std::array ${symbol_name};\n') -BLOB_DEFINITION_SINGLE_LINE = Template( - 'const std::array ${symbol_name}' - ' = { $bytes };') +LINKER_SECTION_TEMPLATE = Template( + 'PW_PLACE_IN_SECTION("${linker_section}")\n') BLOB_DEFINITION_MULTI_LINE = Template( - 'const std::array ${symbol_name}' - ' = {\n${bytes_lines}\n};') + '\n${section_attr}' + '${alignas}constexpr std::array ${symbol_name}' + ' = {\n${bytes_lines}\n};\n') BYTES_PER_LINE = 4 @@ -68,15 +75,24 @@ class Blob(NamedTuple): symbol_name: str file_path: Path linker_section: Optional[str] + alignas: Optional[str] = None + + @staticmethod + def from_dict(blob_dict: dict) -> 'Blob': + return Blob(blob_dict['symbol_name'], Path(blob_dict['file_path']), + blob_dict.get('linker_section'), blob_dict.get('alignas')) -def parse_args(): +def parse_args() -> dict: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--blob-file', type=Path, required=True, help=('Path to json file containing the list of blobs ' 'to generate.')) + parser.add_argument('--header-include', + required=True, + help='Path to use in #includes for the header') parser.add_argument('--out-source', type=Path, required=True, @@ -91,7 +107,7 @@ def parse_args(): help=('Optional namespace that blobs will be scoped' 'within.')) - return parser.parse_args() + return vars(parser.parse_args()) def split_into_chunks( @@ -116,75 +132,65 @@ def header_from_blobs(blobs: Iterable[Blob], lines.append( BLOB_DECLARATION_TEMPLATE.substitute(symbol_name=blob.symbol_name, size_bytes=len(data))) - lines.append('') if namespace: lines.append(NAMESPACE_CLOSE_TEMPLATE.substitute(namespace=namespace)) - return '\n'.join(lines) + return ''.join(lines) -def array_def_from_blob_data(symbol_name: str, blob_data: bytes) -> str: +def array_def_from_blob_data(blob: Blob, blob_data: bytes) -> str: """Generates an array definition for the given blob data.""" - byte_strs = ['std::byte{{0x{:02X}}}'.format(b) for b in blob_data] + if blob.linker_section: + section_attr = LINKER_SECTION_TEMPLATE.substitute( + linker_section=blob.linker_section) + else: + section_attr = '' - # Try to fit the blob definition on a single line first. - single_line_def = BLOB_DEFINITION_SINGLE_LINE.substitute( - symbol_name=symbol_name, - size_bytes=len(blob_data), - bytes=', '.join(byte_strs)) - if len(single_line_def) <= 80: - return single_line_def + byte_strs = ['std::byte{{0x{:02X}}}'.format(b) for b in blob_data] - # Blob definition didn't fit on a single line; do multi-line. lines = [] for byte_strs_for_line in split_into_chunks(byte_strs, BYTES_PER_LINE): bytes_segment = ', '.join(byte_strs_for_line) - lines.append(f' {bytes_segment},') - # Removing the trailing comma from the final line of bytes. - lines[-1] = lines[-1].rstrip(',') + lines.append(f' {bytes_segment},') - return BLOB_DEFINITION_MULTI_LINE.substitute(symbol_name=symbol_name, - size_bytes=len(blob_data), - bytes_lines='\n'.join(lines)) + return BLOB_DEFINITION_MULTI_LINE.substitute( + section_attr=section_attr, + alignas=f'alignas({blob.alignas}) ' if blob.alignas else '', + symbol_name=blob.symbol_name, + size_bytes=len(blob_data), + bytes_lines='\n'.join(lines)) def source_from_blobs(blobs: Iterable[Blob], - header_path: Path, + header_path: str, namespace: Optional[str] = None) -> str: """Generate the contents of a C++ source file from blobs.""" lines = [SOURCE_PREFIX_TEMPLATE.substitute(header_path=header_path)] if namespace: lines.append(NAMESPACE_OPEN_TEMPLATE.substitute(namespace=namespace)) for blob in blobs: - if blob.linker_section: - lines.append( - LINKER_SECTION_TEMPLATE.substitute( - linker_section=blob.linker_section)) data = blob.file_path.read_bytes() - lines.append(array_def_from_blob_data(blob.symbol_name, data)) - lines.append('') + lines.append(array_def_from_blob_data(blob, data)) if namespace: lines.append(NAMESPACE_CLOSE_TEMPLATE.substitute(namespace=namespace)) - return '\n'.join(lines) + return ''.join(lines) -def load_blobs(blob_file: Path) -> Iterable[Blob]: +def load_blobs(blob_file: Path) -> Sequence[Blob]: with blob_file.open() as blob_fp: - return [ - Blob(b["symbol_name"], Path(b["file_path"]), - b.get("linker_section")) for b in json.load(blob_fp) - ] + return [Blob.from_dict(blob) for blob in json.load(blob_fp)] def main(blob_file: Path, + header_include: str, out_source: Path, out_header: Path, namespace: Optional[str] = None) -> None: blobs = load_blobs(blob_file) out_header.write_text(header_from_blobs(blobs, namespace)) - out_source.write_text(source_from_blobs(blobs, out_header, namespace)) + out_source.write_text(source_from_blobs(blobs, header_include, namespace)) if __name__ == '__main__': - main(**vars(parse_args())) + main(**parse_args()) diff --git a/pw_build/test_blob_0123.bin b/pw_build/test_blob_0123.bin new file mode 100644 index 0000000000000000000000000000000000000000..eaf36c1daccfdf325514461cd1a2ffbc139b5464 GIT binary patch literal 4 LcmZQzWMT#Y01f~L literal 0 HcmV?d00001 diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py index b7e4c733a..d71928f82 100755 --- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py +++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py @@ -541,6 +541,7 @@ def edit_compile_commands(in_path: Path, out_path: Path, r'\bpackage.json$', r'\byarn.lock$', # Data files + r'\.bin$', r'\.elf$', r'\.gif$', r'\.jpg$',