diff options
Diffstat (limited to 'pw_build_mcuxpresso')
-rw-r--r-- | pw_build_mcuxpresso/docs.rst | 126 | ||||
-rw-r--r-- | pw_build_mcuxpresso/py/BUILD.gn | 3 | ||||
-rw-r--r-- | pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py | 42 | ||||
-rw-r--r-- | pw_build_mcuxpresso/py/pw_build_mcuxpresso/bazel.py | 89 | ||||
-rw-r--r-- | pw_build_mcuxpresso/py/pw_build_mcuxpresso/components.py | 126 | ||||
-rw-r--r-- | pw_build_mcuxpresso/py/pw_build_mcuxpresso/gn.py | 59 | ||||
-rw-r--r-- | pw_build_mcuxpresso/py/setup.py | 18 | ||||
-rw-r--r-- | pw_build_mcuxpresso/py/tests/components_test.py | 28 |
8 files changed, 343 insertions, 148 deletions
diff --git a/pw_build_mcuxpresso/docs.rst b/pw_build_mcuxpresso/docs.rst index 180587121..1302d5e0c 100644 --- a/pw_build_mcuxpresso/docs.rst +++ b/pw_build_mcuxpresso/docs.rst @@ -1,10 +1,10 @@ .. _module-pw_build_mcuxpresso: -------------------- +=================== pw_build_mcuxpresso -------------------- +=================== -The ``pw_build_mcuxpresso`` module provides helper utilizies for building a +The ``pw_build_mcuxpresso`` module provides helper utilities for building a target based on an NXP MCUXpresso SDK. The GN build files live in ``third_party/mcuxpresso`` but are documented here. @@ -12,21 +12,51 @@ The rationale for keeping the build files in ``third_party`` is that code depending on an MCUXpresso SDK can clearly see that their dependency is on third party, not pigweed code. +----------------------- Using an MCUXpresso SDK -======================= +----------------------- An MCUXpresso SDK consists of a number of components, each of which has a set -of sources, headers, pre-processor defines, and dependencies on other +of sources, headers, preprocessor defines, and dependencies on other components. These are all described in an XML "manifest" file included in the SDK package. To use the SDK within a Pigweed project, the set of components you need must be -combined into a ``pw_source_set`` that you can depend on. This source set will -include all of the sources and headers, along with necessary pre-processor -defines, for those components and their dependencies. +combined into a library that you can depend on. This library will include all of +the sources and headers, along with necessary preprocessor defines, for those +components and their dependencies. -The source set is defined using the ``pw_mcuxpresso_sdk`` template, providing -the path to the ``manifest`` XML, along with the names of the components you -wish to ``include``. +Optional components +=================== +Including components will include all of their required dependencies. Where the +components you include have optional dependencies, they must be satisfied by the +set of components you include otherwise the library generation will fail with an +error. + +Excluding components +==================== +Components can be excluded from the generated source set, for example to +suppress errors about optional dependencies your project does not need, or to +prevent an unwanted component dependency from being introduced into your +project. + +mcuxpresso_builder +================== +``mcuxpresso_builder`` is a utility installed into the environment that is used +by the GN build scripts in ``third_party/mcuxpresso``, or directly by you to +generate rules for the Bazel build. + +Usage is documented for each build system in the relevant section. + +------------ +The GN build +------------ +Using an MCUxpresso SDK within a Pigweed project that uses the GN Build system +involves the creation of one or more ``pw_source_set`` targets you can depend on +in your executable targets. + +These source sets sets are defined using the ``pw_mcuxpresso_sdk`` template. +Provide the path to the ``manifest`` XML, along with the names of the components +you wish to ``include``. .. code-block:: text @@ -46,22 +76,9 @@ wish to ``include``. deps = [ ":sample_project_sdk" ] } -Where the components you include have optional dependencies, they must be -satisfied by the set of components you include otherwise the GN generation will -fail with an error. - -Excluding components --------------------- -Components can be excluded from the generated source set, for example to -suppress errors about optional dependencies your project does not need, or to -prevent an unwanted component dependency from being introduced into your -project. - -This is accomplished by providing the list of components to ``exclude`` as an -argument to the template. - -For example to replace the FreeRTOS kernel bundled with the MCUXpresso SDK with -the Pigweed third-party target: +To exclude components, provide the list to ``exclude`` as an argument to the +template. For example to replace the FreeRTOS kernel bundled with the MCUXpresso +SDK with the Pigweed third-party target: .. code-block:: text @@ -72,7 +89,7 @@ the Pigweed third-party target: } Introducing dependencies ------------------------- +======================== As seen above, the generated source set can have dependencies added by passing the ``public_deps`` (or ``deps``) arguments to the template. @@ -85,7 +102,7 @@ SDK. To resolve circular dependencies, in addition to the generated source set, two configs named with the ``__defines`` and ``__includes`` suffixes on the template -name are generated, to provide the pre-processor defines and include paths that +name are generated, to provide the preprocessor defines and include paths that the source set uses. .. code-block:: text @@ -111,22 +128,57 @@ the source set uses. mcuxpresso_builder ================== -``mcuxpresso_builder`` is a utility that contains the backend scripts used by -the GN build scripts in ``third_party/mcuxpresso``. You should only need to -interact with ``mcuxpresso_builder`` directly if you are doing something custom. +For the GN build, this utility is invoked by the ``pw_mcuxpresso_sdk`` template. +You should only need to interact with ``mcuxpresso_builder`` directly if you are +doing something custom. -project -------- -This command outputs a GN scope describing the result of expanding the set of -included and excluded components. +The ``gn`` subcommand outputs a GN scope describing the result of expanding the +set of included and excluded components. The ``--prefix`` option specifies the GN location of the SDK files. .. code-block:: bash - mcuxpresso_builder project /path/to/manifest.xml \ + mcuxpresso_builder gn /path/to/manifest.xml \ --include project_template.evkmimxrt595.MIMXRT595S \ --include utility.debug_console.MIMXRT595S \ --include component.serial_manager_uart.MIMXRT595S \ --exclude middleware.freertos-kernel.MIMXRT595S \ --prefix //path/to/sdk + +--------------- +The Bazel build +--------------- +To use an MCUxpresso SDK within a Pigweed project that uses tha Bazel build +system, you must use the ``mcuxpresso_builder`` tool directly and place its +output in ``BUILD`` or ``BUILD.bazel`` files yourself. + +Provide the path to the manifest XML, the ``--name`` of the ``cc_library`` to +create, along with the names of the components you wish to ``--include`` or +``--exclude``. + +.. code-block:: bash + + mcuxpresso_builder bazel /path/to/manifest.xml \ + --name example_sdk \ + --include project_template.evkmimxrt595.MIMXRT595S \ + --include utility.debug_console.MIMXRT595S \ + --include component.serial_manager_uart.MIMXRT595S \ + --exclude middleware.freertos-kernel.MIMXRT595S + + +Place the resulting output in a ``BUILD`` file, and then modify your +``WORKSPACE`` to associate this build file with the path to the MCUxpresso SDK +checkout. + +.. code-block:: python + + new_local_repository( + name = "mcuxpresso_sdk", + build_file = "//third_party/mcuxpresso_sdk/BUILD", + path = "third_party/evkmimxrt595/sdk", + ) + +To add other dependencies, compiler definitions, etc. it is recommended that +you do so by creating a new target, and add a dependency to it, rather than +modifying the generated targets. diff --git a/pw_build_mcuxpresso/py/BUILD.gn b/pw_build_mcuxpresso/py/BUILD.gn index e640bd372..274fbe4a9 100644 --- a/pw_build_mcuxpresso/py/BUILD.gn +++ b/pw_build_mcuxpresso/py/BUILD.gn @@ -20,12 +20,13 @@ pw_python_package("py") { setup = [ "pyproject.toml", "setup.cfg", - "setup.py", ] sources = [ "pw_build_mcuxpresso/__init__.py", "pw_build_mcuxpresso/__main__.py", + "pw_build_mcuxpresso/bazel.py", "pw_build_mcuxpresso/components.py", + "pw_build_mcuxpresso/gn.py", ] tests = [ "tests/components_test.py" ] pylintrc = "$dir_pigweed/.pylintrc" diff --git a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py index c537cf7e4..c2e816898 100644 --- a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py +++ b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py @@ -19,10 +19,12 @@ import pathlib import sys try: - from pw_build_mcuxpresso import components + from pw_build_mcuxpresso import bazel, components, gn except ImportError: # Load from this directory if pw_build_mcuxpresso is not available. import components # type: ignore + import bazel # type: ignore + import gn # type: ignore def _parse_args() -> argparse.Namespace: @@ -33,13 +35,22 @@ def _parse_args() -> argparse.Namespace: dest='command', metavar='<command>', required=True ) - project_parser = subparsers.add_parser( - 'project', help='output components of an MCUXpresso project' + subparser = subparsers.add_parser( + 'gn', help='output components of an MCUXpresso project as GN scope' ) - project_parser.add_argument('manifest_filename', type=pathlib.Path) - project_parser.add_argument('--include', type=str, action='append') - project_parser.add_argument('--exclude', type=str, action='append') - project_parser.add_argument('--prefix', dest='path_prefix', type=str) + subparser.add_argument('manifest_filename', type=pathlib.Path) + subparser.add_argument('--include', type=str, action='append') + subparser.add_argument('--exclude', type=str, action='append') + subparser.add_argument('--prefix', dest='path_prefix', type=str) + + subparser = subparsers.add_parser( + 'bazel', help='output an MCUXpresso project as a bazel target' + ) + subparser.add_argument('manifest_filename', type=pathlib.Path) + subparser.add_argument('--name', dest='bazel_name', type=str, required=True) + subparser.add_argument('--include', type=str, action='append') + subparser.add_argument('--exclude', type=str, action='append') + subparser.add_argument('--prefix', dest='path_prefix', type=str) return parser.parse_args() @@ -48,12 +59,17 @@ def main(): """Main command line function.""" args = _parse_args() - if args.command == 'project': - components.project( - args.manifest_filename, - include=args.include, - exclude=args.exclude, - path_prefix=args.path_prefix, + project = components.Project.from_file( + args.manifest_filename, + include=args.include, + exclude=args.exclude, + ) + + if args.command == 'gn': + gn.gn_output(project, path_prefix=args.path_prefix) + if args.command == 'bazel': + bazel.bazel_output( + project, name=args.bazel_name, path_prefix=args.path_prefix ) sys.exit(0) diff --git a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/bazel.py b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/bazel.py new file mode 100644 index 000000000..3d6d3a9b5 --- /dev/null +++ b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/bazel.py @@ -0,0 +1,89 @@ +# Copyright 2023 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. +"""Bazel output support.""" + +from typing import Any, List, Optional + +import pathlib + +try: + from pw_build_mcuxpresso.components import Project +except ImportError: + # Load from this directory if pw_build_mcuxpresso is not available. + from components import Project # type: ignore + + +def _bazel_str(val: Any) -> str: + """Returns string in Bazel format with correct escaping.""" + return str(val).replace('"', r'\"').replace('$', r'\$') + + +def _bazel_str_out(name: str, val: Any, indent: int = 0) -> None: + """Outputs string in Bazel format with correct escaping.""" + print(' ' * indent + f'{name} = "{_bazel_str(val)}",') + + +def _bazel_str_list_out(name: str, vals: List[Any], indent: int = 0) -> None: + """Outputs list of strings in Bazel format with correct escaping.""" + if not vals: + return + + print(' ' * indent + f'{name} = [') + for val in vals: + print(' ' * (indent + 1) + f'"{_bazel_str(val)}",') + print(' ' * indent + '],') + + +def _bazel_path_list_out( + name: str, + vals: List[pathlib.Path], + path_prefix: Optional[str] = None, + indent: int = 0, +) -> None: + """Outputs list of paths in Bazel format with common prefix.""" + if path_prefix is not None: + str_vals = [f'{path_prefix}{str(val)}' for val in vals] + else: + str_vals = [str(f) for f in vals] + + _bazel_str_list_out(name, sorted(set(str_vals)), indent=indent) + + +def bazel_output( + project: Project, name: str, path_prefix: Optional[str] = None +): + """Output Bazel target for a project with the specified components. + + Args: + project: MCUXpresso project to output. + name: target name to output. + path_prefix: string prefix to prepend to all paths. + """ + print('cc_library(') + _bazel_str_out('name', name, indent=1) + _bazel_path_list_out( + 'srcs', + project.sources + project.libs, + path_prefix=path_prefix, + indent=1, + ) + _bazel_path_list_out( + 'hdrs', project.headers, path_prefix=path_prefix, indent=1 + ) + _bazel_str_list_out('defines', project.defines, indent=1) + _bazel_path_list_out( + 'includes', project.include_dirs, path_prefix=path_prefix, indent=1 + ) + + print(')') diff --git a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/components.py b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/components.py index a7223a434..c664d3f0e 100644 --- a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/components.py +++ b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/components.py @@ -13,37 +13,13 @@ # the License. """Finds components for a given manifest.""" -from typing import Any, List, Optional, Tuple +from typing import List, Optional, Tuple import pathlib import sys import xml.etree.ElementTree -def _gn_str_out(name: str, val: Any): - """Outputs scoped string in GN format.""" - print(f'{name} = "{val}"') - - -def _gn_list_str_out(name: str, val: List[Any]): - """Outputs list of strings in GN format with correct escaping.""" - list_str = ','.join( - '"' + str(x).replace('"', r'\"').replace('$', r'\$') + '"' for x in val - ) - print(f'{name} = [{list_str}]') - - -def _gn_list_path_out( - name: str, val: List[pathlib.Path], path_prefix: Optional[str] = None -): - """Outputs list of paths in GN format with common prefix.""" - if path_prefix is not None: - str_val = list(f'{path_prefix}/{str(d)}' for d in val) - else: - str_val = list(str(d) for d in val) - _gn_list_str_out(name, str_val) - - def get_component( root: xml.etree.ElementTree.Element, component_id: str ) -> Tuple[Optional[xml.etree.ElementTree.Element], Optional[pathlib.Path]]: @@ -488,42 +464,68 @@ def create_project( ) -def project( - manifest_path: pathlib.Path, - include: Optional[List[str]] = None, - exclude: Optional[List[str]] = None, - path_prefix: Optional[str] = None, -): - """Output GN scope for a project with the specified components. +class Project: + """Self-contained MCUXpresso project. - Args: - manifest_path: path to SDK manifest XML. - include: list of component ids included in the project. - exclude: list of component ids excluded from the project. - path_prefix: string prefix to prepend to all paths. + Properties: + component_ids: list of component ids compromising the project. + defines: list of compiler definitions to build the project. + include_dirs: list of include directory paths needed for the project. + headers: list of header paths exported by the project. + sources: list of source file paths built as part of the project. + libs: list of libraries linked to the project. + dependencies_satisfied: True if the project dependencies are satisfied. """ - assert include is not None, "Project must include at least one component." - - tree = xml.etree.ElementTree.parse(manifest_path) - root = tree.getroot() - - ( - component_ids, - defines, - include_dirs, - headers, - sources, - libs, - ) = create_project(root, include, exclude=exclude) - - for component_id in component_ids: - if not check_dependencies( - root, component_id, component_ids, exclude=exclude - ): - return - - _gn_list_str_out('defines', defines) - _gn_list_path_out('include_dirs', include_dirs, path_prefix=path_prefix) - _gn_list_path_out('public', headers, path_prefix=path_prefix) - _gn_list_path_out('sources', sources, path_prefix=path_prefix) - _gn_list_path_out('libs', libs, path_prefix=path_prefix) + + @classmethod + def from_file( + cls, + manifest_path: pathlib.Path, + include: Optional[List[str]] = None, + exclude: Optional[List[str]] = None, + ): + """Create a self-contained project with the specified components. + + Args: + manifest_path: path to SDK manifest XML. + include: list of component ids included in the project. + exclude: list of component ids excluded from the project. + """ + tree = xml.etree.ElementTree.parse(manifest_path) + root = tree.getroot() + return cls(root, include=include, exclude=exclude) + + def __init__( + self, + manifest: xml.etree.ElementTree.Element, + include: Optional[List[str]] = None, + exclude: Optional[List[str]] = None, + ): + """Create a self-contained project with the specified components. + + Args: + manifest: parsed manifest XML. + include: list of component ids included in the project. + exclude: list of component ids excluded from the project. + """ + assert ( + include is not None + ), "Project must include at least one component." + + ( + self.component_ids, + self.defines, + self.include_dirs, + self.headers, + self.sources, + self.libs, + ) = create_project(manifest, include, exclude=exclude) + + for component_id in self.component_ids: + if not check_dependencies( + manifest, component_id, self.component_ids, exclude=exclude + ): + self.dependencies_satisfied = False + return + + self.dependencies_satisfied = True diff --git a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/gn.py b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/gn.py new file mode 100644 index 000000000..202c4f774 --- /dev/null +++ b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/gn.py @@ -0,0 +1,59 @@ +# Copyright 2023 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. +"""GN output support.""" + +from typing import Any, List, Optional + +import pathlib + +try: + from pw_build_mcuxpresso.components import Project +except ImportError: + # Load from this directory if pw_build_mcuxpresso is not available. + from components import Project # type: ignore + + +def _gn_list_str_out(name: str, val: List[Any]): + """Outputs list of strings in GN format with correct escaping.""" + list_str = ','.join( + '"' + str(x).replace('"', r'\"').replace('$', r'\$') + '"' for x in val + ) + print(f'{name} = [{list_str}]') + + +def _gn_list_path_out( + name: str, val: List[pathlib.Path], path_prefix: Optional[str] = None +): + """Outputs list of paths in GN format with common prefix.""" + if path_prefix is not None: + str_val = list(f'{path_prefix}/{str(d)}' for d in val) + else: + str_val = list(str(d) for d in val) + _gn_list_str_out(name, str_val) + + +def gn_output(project: Project, path_prefix: Optional[str] = None): + """Output GN scope for a project with the specified components. + + Args: + project: MCUXpresso project to output.. + path_prefix: string prefix to prepend to all paths. + """ + _gn_list_str_out('defines', project.defines) + _gn_list_path_out( + 'include_dirs', project.include_dirs, path_prefix=path_prefix + ) + _gn_list_path_out('public', project.headers, path_prefix=path_prefix) + _gn_list_path_out('sources', project.sources, path_prefix=path_prefix) + _gn_list_path_out('libs', project.libs, path_prefix=path_prefix) diff --git a/pw_build_mcuxpresso/py/setup.py b/pw_build_mcuxpresso/py/setup.py deleted file mode 100644 index 694c28691..000000000 --- a/pw_build_mcuxpresso/py/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2021 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. -"""pw_build_mcuxpresso""" - -import setuptools # type: ignore - -setuptools.setup() # Package definition in setup.cfg diff --git a/pw_build_mcuxpresso/py/tests/components_test.py b/pw_build_mcuxpresso/py/tests/components_test.py index 94ca6828b..8b36a448f 100644 --- a/pw_build_mcuxpresso/py/tests/components_test.py +++ b/pw_build_mcuxpresso/py/tests/components_test.py @@ -805,8 +805,8 @@ class CheckDependenciesTest(unittest.TestCase): self.assertEqual(satisfied, True) -class CreateProjectTest(unittest.TestCase): - """create_project tests.""" +class ProjectTest(unittest.TestCase): + """Project tests.""" def test_create_project(self): """test creating a project.""" @@ -904,21 +904,14 @@ class CreateProjectTest(unittest.TestCase): </manifest> ''' root = xml.etree.ElementTree.fromstring(test_manifest_xml) - ( - component_ids, - defines, - include_dirs, - headers, - sources, - libs, - ) = components.create_project( - root, ['test', 'frodo'], exclude=['bilbo'] + project = components.Project( + root, ['test', 'frodo'], exclude=['baz', 'bilbo'] ) - self.assertEqual(component_ids, ['test', 'frodo', 'foo', 'bar']) - self.assertEqual(defines, ['FRODO', 'FOO', 'BAR']) + self.assertEqual(project.component_ids, ['test', 'frodo', 'foo', 'bar']) + self.assertEqual(project.defines, ['FRODO', 'FOO', 'BAR']) self.assertEqual( - include_dirs, + project.include_dirs, [ pathlib.Path('frodo/include'), pathlib.Path('foo/include'), @@ -926,7 +919,7 @@ class CreateProjectTest(unittest.TestCase): ], ) self.assertEqual( - headers, + project.headers, [ pathlib.Path('frodo/include/frodo.h'), pathlib.Path('foo/include/foo.h'), @@ -934,14 +927,15 @@ class CreateProjectTest(unittest.TestCase): ], ) self.assertEqual( - sources, + project.sources, [ pathlib.Path('frodo/src/frodo.cc'), pathlib.Path('foo/src/foo.cc'), pathlib.Path('bar/src/bar.cc'), ], ) - self.assertEqual(libs, [pathlib.Path('frodo/libonering.a')]) + self.assertEqual(project.libs, [pathlib.Path('frodo/libonering.a')]) + self.assertTrue(project.dependencies_satisfied) if __name__ == '__main__': |