diff options
Diffstat (limited to 'zephyr/zmake')
-rw-r--r-- | zephyr/zmake/setup.py | 4 | ||||
-rw-r--r-- | zephyr/zmake/tests/test_project.py | 208 | ||||
-rw-r--r-- | zephyr/zmake/tests/test_toolchains.py | 15 | ||||
-rw-r--r-- | zephyr/zmake/tests/test_util.py | 52 | ||||
-rw-r--r-- | zephyr/zmake/tests/test_version.py | 15 | ||||
-rw-r--r-- | zephyr/zmake/tests/test_zmake.py | 24 | ||||
-rw-r--r-- | zephyr/zmake/zmake/__main__.py | 19 | ||||
-rw-r--r-- | zephyr/zmake/zmake/configlib.py | 40 | ||||
-rw-r--r-- | zephyr/zmake/zmake/project.py | 185 | ||||
-rw-r--r-- | zephyr/zmake/zmake/toolchains.py | 4 | ||||
-rw-r--r-- | zephyr/zmake/zmake/util.py | 63 | ||||
-rw-r--r-- | zephyr/zmake/zmake/version.py | 7 | ||||
-rw-r--r-- | zephyr/zmake/zmake/zmake.py | 167 |
13 files changed, 386 insertions, 417 deletions
diff --git a/zephyr/zmake/setup.py b/zephyr/zmake/setup.py index 4328dc48d7..51001c6695 100644 --- a/zephyr/zmake/setup.py +++ b/zephyr/zmake/setup.py @@ -24,8 +24,8 @@ setuptools.setup( # requirements files see: # https://packaging.python.org/en/latest/requirements.html install_requires=[ - "jsonschema>=3.2.0", - "pyyaml>=3.13", + # Required until chroot upgrades to Python 3.7+. + "dataclasses>=0.6; python_version < '3.7'", ], # To provide executable scripts, use entry points in preference to the # "scripts" keyword. Entry points provide cross-platform support and allow diff --git a/zephyr/zmake/tests/test_project.py b/zephyr/zmake/tests/test_project.py index 2442ceedf6..f7688784e7 100644 --- a/zephyr/zmake/tests/test_project.py +++ b/zephyr/zmake/tests/test_project.py @@ -11,28 +11,13 @@ import hypothesis.strategies as st import pytest import zmake.modules +import zmake.output_packers import zmake.project board_names = st.text(alphabet=set(string.ascii_lowercase) | {"_"}, min_size=1) sets_of_board_names = st.lists(st.lists(board_names, unique=True)) -class TemporaryProject(tempfile.TemporaryDirectory): - """A temporary project wrapper. - - Args: - config: The config dictionary to be used with the project. - """ - - def __init__(self, config): - self.config = config - super().__init__() - - def __enter__(self): - project_path = pathlib.Path(super().__enter__()) - return zmake.project.Project(project_path, config_dict=self.config) - - @hypothesis.given(sets_of_board_names) @hypothesis.settings(deadline=None) def test_find_dts_overlays(modules): @@ -67,21 +52,21 @@ def test_find_dts_overlays(modules): board_file_mapping[board] = files | {file_name} for board, expected_dts_files in board_file_mapping.items(): - with TemporaryProject( - { - "board": board, - "output-type": "elf", - "supported-toolchains": ["llvm"], - "supported-zephyr-versions": ["v2.6"], - } - ) as project: - config = project.find_dts_overlays(dict(enumerate(module_paths))) - - actual_dts_files = set( - config.cmake_defs.get("DTC_OVERLAY_FILE", "").split(";") + project = zmake.project.Project( + zmake.project.ProjectConfig( + project_name=board, + zephyr_board=board, + output_packer=zmake.output_packers.ElfPacker, + supported_toolchains=["llvm"], + project_dir=pathlib.Path("/fakebuild"), ) + ) + config = project.find_dts_overlays(dict(enumerate(module_paths))) + actual_dts_files = set( + config.cmake_defs.get("DTC_OVERLAY_FILE", "").split(";") + ) - assert actual_dts_files == set(map(str, expected_dts_files)) + assert actual_dts_files == set(map(str, expected_dts_files)) setup_modules_and_dispatch(modules, testcase) @@ -101,16 +86,17 @@ def test_prune_modules(modules): for name in zmake.modules.known_modules } - with TemporaryProject( - { - "board": "native_posix", - "output-type": "elf", - "supported-toolchains": ["coreboot-sdk"], - "supported-zephyr-versions": ["v2.6"], - "modules": modules, - } - ) as project: - assert set(project.prune_modules(module_paths)) == set(modules) + project = zmake.project.Project( + zmake.project.ProjectConfig( + project_name="prunetest", + zephyr_board="native_posix", + output_packer=zmake.output_packers.ElfPacker, + supported_toolchains=["coreboot-sdk"], + project_dir=pathlib.Path("/fake"), + modules=modules, + ), + ) + assert set(project.prune_modules(module_paths)) == set(modules) def test_prune_modules_unavailable(): @@ -122,17 +108,18 @@ def test_prune_modules_unavailable(): "hal_stm32": pathlib.Path("/mod/halstm"), } - with TemporaryProject( - { - "board": "native_posix", - "output-type": "elf", - "supported-toolchains": ["coreboot-sdk"], - "supported-zephyr-versions": ["v2.6"], - "modules": ["hal_stm32", "cmsis"], - } - ) as project: - with pytest.raises(KeyError): - project.prune_modules(module_paths) + project = zmake.project.Project( + zmake.project.ProjectConfig( + project_name="prunetest", + zephyr_board="native_posix", + output_packer=zmake.output_packers.ElfPacker, + supported_toolchains=["coreboot-sdk"], + project_dir=pathlib.Path("/fake"), + modules=["hal_stm32", "cmsis"], + ), + ) + with pytest.raises(KeyError): + project.prune_modules(module_paths) def test_find_projects_empty(tmp_path): @@ -141,33 +128,104 @@ def test_find_projects_empty(tmp_path): assert len(projects) == 0 -YAML_FILE = """ -supported-zephyr-versions: - - v2.6 -supported-toolchains: - - coreboot-sdk -output-type: npcx +CONFIG_FILE_1 = """ +register_raw_project(project_name="one", zephyr_board="one") +register_host_test(test_name="two") +register_npcx_project(project_name="three", zephyr_board="three") +register_binman_project(project_name="four", zephyr_board="four") +""" + +CONFIG_FILE_2 = """ +register_raw_project( + project_name="five", + zephyr_board="foo", + dts_overlays=[here / "gpio.dts"], +) """ def test_find_projects(tmp_path): """Test the find_projects method when there are projects.""" - dir = tmp_path.joinpath("one") - dir.mkdir() - dir.joinpath("zmake.yaml").write_text("board: one\n" + YAML_FILE) - tmp_path.joinpath("two").mkdir() - dir = tmp_path.joinpath("two/a") - dir.mkdir() - dir.joinpath("zmake.yaml").write_text("board: twoa\nis-test: true\n" + YAML_FILE) - dir = tmp_path.joinpath("two/b") - dir.mkdir() - dir.joinpath("zmake.yaml").write_text("board: twob\n" + YAML_FILE) - projects = list(zmake.project.find_projects(tmp_path)) - projects.sort(key=lambda x: x.project_dir) - assert len(projects) == 3 - assert projects[0].project_dir == tmp_path.joinpath("one") - assert projects[1].project_dir == tmp_path.joinpath("two/a") - assert projects[2].project_dir == tmp_path.joinpath("two/b") - assert not projects[0].config.is_test - assert projects[1].config.is_test - assert not projects[2].config.is_test + cf1_dir = tmp_path / "cf1" + cf1_dir.mkdir() + (cf1_dir / "BUILD.py").write_text(CONFIG_FILE_1) + + cf2_bb_dir = tmp_path / "cf2_bb" + cf2_bb_dir.mkdir() + cf2_dir = cf2_bb_dir / "cf2" + cf2_dir.mkdir() + (cf2_dir / "BUILD.py").write_text(CONFIG_FILE_2) + + projects = zmake.project.find_projects(tmp_path) + assert len(projects) == 5 + assert projects["one"].config.project_dir == cf1_dir + assert not projects["one"].config.is_test + + assert projects["test-two"].config.project_dir == cf1_dir + assert projects["test-two"].config.zephyr_board == "native_posix" + assert projects["test-two"].config.is_test + + assert projects["three"].config.project_dir == cf1_dir + assert not projects["three"].config.is_test + assert projects["three"].config.zephyr_board == "three" + + assert projects["four"].config.project_dir == cf1_dir + assert not projects["four"].config.is_test + assert projects["four"].config.zephyr_board == "four" + + assert projects["five"].config.project_dir == cf2_dir + assert not projects["five"].config.is_test + assert projects["five"].config.zephyr_board == "foo" + + +def test_find_projects_name_conflict(tmp_path): + """When two projects define the same name, that should be an error.""" + cf1_dir = tmp_path / "cf1" + cf1_dir.mkdir() + (cf1_dir / "BUILD.py").write_text(CONFIG_FILE_2) + + cf2_dir = tmp_path / "cf2" + cf2_dir.mkdir() + (cf2_dir / "BUILD.py").write_text(CONFIG_FILE_2) + + with pytest.raises(KeyError): + zmake.project.find_projects(tmp_path) + + +@pytest.mark.parametrize( + ("actual_files", "config_files", "expected_files"), + [ + (["prj_link.conf"], [], []), + (["prj.conf"], [], ["prj.conf"]), + ( + ["prj.conf", "cfg.conf"], + ["prj.conf", "cfg.conf"], + ["prj.conf", "cfg.conf"], + ), + ( + ["prj.conf", "prj_samus.conf", "prj_link.conf"], + ["prj_link.conf"], + ["prj.conf", "prj_link.conf"], + ), + ], +) +def test_kconfig_files(tmp_path, actual_files, config_files, expected_files): + for name in actual_files: + (tmp_path / name).write_text("") + + project = zmake.project.Project( + zmake.project.ProjectConfig( + project_name="samus", + zephyr_board="lm4", + output_packer=zmake.output_packers.RawBinPacker, + supported_toolchains=["coreboot-sdk"], + project_dir=tmp_path, + kconfig_files=[tmp_path / name for name in config_files], + ), + ) + + builds = list(project.iter_builds()) + assert len(builds) == 1 + + _, config = builds[0] + assert sorted(f.name for f in config.kconfig_files) == sorted(expected_files) diff --git a/zephyr/zmake/tests/test_toolchains.py b/zephyr/zmake/tests/test_toolchains.py index 515f54a112..fb1953052a 100644 --- a/zephyr/zmake/tests/test_toolchains.py +++ b/zephyr/zmake/tests/test_toolchains.py @@ -7,6 +7,7 @@ import pathlib import pytest +import zmake.output_packers import zmake.project as project import zmake.toolchains as toolchains @@ -62,18 +63,18 @@ def zephyr_exists(mockfs): @pytest.fixture def fake_project(tmp_path): return project.Project( - tmp_path, - config_dict={ - "board": "foo", - "supported-zephyr-versions": ["v2.6"], - "supported-toolchains": [ + project.ProjectConfig( + project_name="foo", + zephyr_board="foo", + supported_toolchains=[ "coreboot-sdk", "host", "llvm", "zephyr", ], - "output-type": "raw", - }, + output_packer=zmake.output_packers.RawBinPacker, + project_dir=tmp_path, + ), ) diff --git a/zephyr/zmake/tests/test_util.py b/zephyr/zmake/tests/test_util.py index 0c4cd4dda5..438c5efcf0 100644 --- a/zephyr/zmake/tests/test_util.py +++ b/zephyr/zmake/tests/test_util.py @@ -3,7 +3,6 @@ # found in the LICENSE file. import pathlib -import re import tempfile import hypothesis @@ -13,57 +12,6 @@ import pytest import zmake.util as util # Strategies for use with hypothesis -relative_path = st.from_regex( - regex=re.compile(r"\A\w{1,255}(/\w{1,255}){0,15}\Z", re.ASCII) -) - - -@hypothesis.given(relative_path, relative_path, relative_path) -@hypothesis.settings(deadline=60000) -def test_resolve_build_dir_with_build_dir( - platform_ec_subdir, project_subdir, build_subdir -): - with tempfile.TemporaryDirectory() as temp_dir_name: - platform_ec_dir = pathlib.Path(temp_dir_name) / platform_ec_subdir - build_dir = util.resolve_build_dir( - platform_ec_dir=platform_ec_dir, - project_dir=platform_ec_dir / project_subdir, - build_dir=platform_ec_dir / build_subdir, - ) - - assert build_dir == platform_ec_dir / build_subdir - - -@hypothesis.given(relative_path, relative_path) -@hypothesis.settings(deadline=60000) -def test_resolve_build_dir_invalid_project(platform_ec_subdir, project_subdir): - try: - with tempfile.TemporaryDirectory() as temp_dir_name: - platform_ec_dir = pathlib.Path(temp_dir_name) / platform_ec_subdir - util.resolve_build_dir( - platform_ec_dir=platform_ec_dir, - project_dir=platform_ec_dir / project_subdir, - build_dir=None, - ) - pytest.fail() - except Exception: - pass - - -@hypothesis.given(relative_path, relative_path) -@hypothesis.settings(deadline=60000) -def test_resolve_build_dir_from_project(platform_ec_subdir, project_subdir): - with tempfile.TemporaryDirectory() as temp_dir_name: - platform_ec_dir = pathlib.Path(temp_dir_name) / platform_ec_subdir - project_dir = platform_ec_dir / project_subdir - project_dir.mkdir(parents=True) - (project_dir / "zmake.yaml").touch() - build_dir = util.resolve_build_dir( - platform_ec_dir=platform_ec_dir, project_dir=project_dir, build_dir=None - ) - assert build_dir == platform_ec_dir / "build" / project_subdir - - version_integers = st.integers(min_value=0) version_tuples = st.tuples(version_integers, version_integers, version_integers) diff --git a/zephyr/zmake/tests/test_version.py b/zephyr/zmake/tests/test_version.py index a238a8ac02..b2c6b43fec 100644 --- a/zephyr/zmake/tests/test_version.py +++ b/zephyr/zmake/tests/test_version.py @@ -8,6 +8,7 @@ import unittest.mock as mock import pytest +import zmake.output_packers import zmake.project import zmake.version as version @@ -51,13 +52,13 @@ def _setup_example_repos(tmp_path): project_path.mkdir() project = zmake.project.Project( - project_path, - config_dict={ - "board": "foo", - "output-type": "raw", - "supported-toolchains": ["coreboot-sdk"], - "supported-zephyr-versions": ["v2.6"], - }, + zmake.project.ProjectConfig( + project_name="prj", + zephyr_board="foo", + output_packer=zmake.output_packers.RawBinPacker, + supported_toolchains=["coreboot-sdk"], + project_dir=project_path, + ), ) # Has one commit. zephyr_base = tmp_path / "zephyr_base" diff --git a/zephyr/zmake/tests/test_zmake.py b/zephyr/zmake/tests/test_zmake.py index 163159b9c5..f735d942ed 100644 --- a/zephyr/zmake/tests/test_zmake.py +++ b/zephyr/zmake/tests/test_zmake.py @@ -18,6 +18,7 @@ from testfixtures import LogCapture import zmake.build_config import zmake.jobserver import zmake.multiproc as multiproc +import zmake.output_packers import zmake.project import zmake.toolchains import zmake.zmake as zm @@ -33,10 +34,14 @@ class FakeProject: def __init__(self): self.packer = mock.Mock() self.packer.pack_firmware = mock.Mock(return_value=[]) - self.project_dir = pathlib.Path("FakeProjectDir") - self.config = mock.Mock() - self.config.supported_zephyr_versions = [(2, 5)] + self.config = zmake.project.ProjectConfig( + project_name="fakeproject", + zephyr_board="fakeboard", + supported_toolchains=["llvm"], + output_packer=zmake.output_packers.ElfPacker, + project_dir=pathlib.Path("FakeProjectDir"), + ) @staticmethod def iter_builds(): @@ -123,12 +128,10 @@ def do_test_with_log_level(log_level, use_configure=False, fnames=None): re.compile(r".*build-rw"): get_test_filepath("rw"), } zephyr_base = mock.Mock() - zephyr_root = mock.Mock() zmk = zm.Zmake( jobserver=FakeJobserver(fnames), zephyr_base=zephyr_base, - zephyr_root=zephyr_root, ) with LogCapture(level=log_level) as cap: @@ -142,13 +145,16 @@ VERSION_TWEAK = 0 EXTRAVERSION = """ ) + (pathlib.Path(tmpname) / "project_name.txt").write_text("fakeproject") zephyr_base.resolve = mock.Mock(return_value=pathlib.Path(tmpname)) with patch("zmake.version.get_version_string", return_value="123"): - with patch.object(zmake.project, "Project", return_value=FakeProject()): + with patch.object( + zmake.project, + "find_projects", + return_value={"fakeproject": FakeProject()}, + ): if use_configure: - zmk.configure( - pathlib.Path(tmpname), build_dir=pathlib.Path("build") - ) + zmk.configure("fakeproject", build_dir=pathlib.Path("build")) else: with patch("zmake.version.write_version_header", autospec=True): zmk.build(pathlib.Path(tmpname)) diff --git a/zephyr/zmake/zmake/__main__.py b/zephyr/zmake/zmake/__main__.py index ea639584cc..aef897d1d3 100644 --- a/zephyr/zmake/zmake/__main__.py +++ b/zephyr/zmake/zmake/__main__.py @@ -161,21 +161,11 @@ def main(argv=None): parser.add_argument( "--zephyr-base", type=pathlib.Path, help="Path to Zephyr OS repository" ) - parser.add_argument( - "--zephyr-root", - type=pathlib.Path, - help="Path to Zephyr OS repos, must contain subdirs like v1.2", - ) sub = parser.add_subparsers(dest="subcommand", help="Subcommand") sub.required = True configure = sub.add_parser("configure") - configure.add_argument( - "--ignore-unsupported-zephyr-version", - action="store_true", - help="Don't warn about using an unsupported Zephyr version", - ) configure.add_argument("-t", "--toolchain", help="Name of toolchain to use") configure.add_argument( "--bringup", @@ -184,6 +174,12 @@ def main(argv=None): help="Enable bringup debugging features", ) configure.add_argument( + "--allow-warnings", + action="store_true", + default=False, + help="Do not treat warnings as errors", + ) + configure.add_argument( "-B", "--build-dir", type=pathlib.Path, help="Build directory" ) configure.add_argument( @@ -200,7 +196,8 @@ def main(argv=None): help="Test the .elf file after configuration", ) configure.add_argument( - "project_dir", type=pathlib.Path, help="Path to the project to build" + "project_name_or_dir", + help="Path to the project to build", ) configure.add_argument( "-c", diff --git a/zephyr/zmake/zmake/configlib.py b/zephyr/zmake/zmake/configlib.py new file mode 100644 index 0000000000..3c6aa649c5 --- /dev/null +++ b/zephyr/zmake/zmake/configlib.py @@ -0,0 +1,40 @@ +# Copyright 2021 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""This module defines helpers accessible to all BUILD.py files.""" + +import zmake.output_packers + + +def _register_project(**kwargs): + kwargs.setdefault("project_dir", here) # noqa: F821 + register_project(**kwargs) # noqa: F821 + + +def register_host_project(**kwargs): + kwargs.setdefault("zephyr_board", "native_posix") + kwargs.setdefault("supported_toolchains", ["llvm", "host"]) + kwargs.setdefault("output_packer", zmake.output_packers.ElfPacker) + _register_project(**kwargs) + + +def register_host_test(test_name, **kwargs): + kwargs.setdefault("is_test", True) + register_host_project(project_name="test-{}".format(test_name), **kwargs) + + +def register_raw_project(**kwargs): + kwargs.setdefault("supported_toolchains", ["coreboot-sdk", "zephyr"]) + kwargs.setdefault("output_packer", zmake.output_packers.RawBinPacker) + _register_project(**kwargs) + + +def register_binman_project(**kwargs): + kwargs.setdefault("output_packer", zmake.output_packers.BinmanPacker) + register_raw_project(**kwargs) + + +def register_npcx_project(**kwargs): + kwargs.setdefault("output_packer", zmake.output_packers.NpcxPacker) + register_binman_project(**kwargs) diff --git a/zephyr/zmake/zmake/project.py b/zephyr/zmake/zmake/project.py index 7ffe4ebc19..0e2e97fd14 100644 --- a/zephyr/zmake/zmake/project.py +++ b/zephyr/zmake/zmake/project.py @@ -3,24 +3,14 @@ # found in the LICENSE file. """Module for project config wrapper object.""" +import dataclasses import logging import pathlib -import warnings - -import yaml import zmake.build_config as build_config +import zmake.configlib as configlib import zmake.modules as modules -import zmake.output_packers as packers import zmake.toolchains as toolchains -import zmake.util as util - -# The version of jsonschema in the chroot has a bunch of -# DeprecationWarnings that fire when we import it. Suppress these -# during the import to keep the noise down. -with warnings.catch_warnings(): - warnings.simplefilter("ignore") - import jsonschema def module_dts_overlay_name(modpath, board_name): @@ -36,121 +26,85 @@ def module_dts_overlay_name(modpath, board_name): return modpath / "zephyr" / "dts" / "board-overlays" / "{}.dts".format(board_name) -def find_projects(root_dir): - """Finds all zmake projects in root_dir. +def load_config_file(path): + """Load a BUILD.py config file and create associated projects. Args: - root_dir: the root dir as a pathlib.Path object + path: A pathlib.Path to the BUILD.py file. - Yields: - Project: The next project found. + Returns: + A list of Project objects specified by the file. """ - logging.info("Finding zmake targets under '%s'.", root_dir) - for path in pathlib.Path(root_dir).rglob("zmake.yaml"): - yield Project(path.parent) + projects = [] + def register_project(**kwargs): + projects.append(Project(ProjectConfig(**kwargs))) -class ProjectConfig: - """An object wrapping zmake.yaml.""" - - validator = jsonschema.Draft7Validator - schema = { - "type": "object", - "required": [ - "board", - "output-type", - "supported-toolchains", - "supported-zephyr-versions", - ], - "properties": { - "supported-zephyr-versions": { - "type": "array", - "items": { - "type": "string", - "enum": ["v2.6", "v2.7", "v2.8"], - }, - "minItems": 1, - "uniqueItems": True, - }, - "board": { - "type": "string", - }, - "modules": { - "type": "array", - "items": { - "type": "string", - "enum": list(modules.known_modules), - }, - }, - "output-type": { - "type": "string", - "enum": list(packers.packer_registry), - }, - "supported-toolchains": { - "type": "array", - "items": { - "type": "string", - "enum": list(toolchains.support_classes), - }, - }, - "is-test": { - "type": "boolean", - }, - "dts-overlays": { - "type": "array", - "items": { - "type": "string", - }, - }, - }, + # The Python environment passed to the config file. + config_globals = { + "register_project": register_project, + "here": path.parent.resolve(), } - def __init__(self, config_dict): - self.validator.check_schema(self.schema) - jsonschema.validate(config_dict, self.schema, cls=self.validator) - self.config_dict = config_dict + # First, load the global helper functions. + code = compile( + pathlib.Path(configlib.__file__).read_bytes(), + configlib.__file__, + "exec", + ) + exec(code, config_globals) - @property - def supported_zephyr_versions(self): - return [ - util.parse_zephyr_version(x) - for x in self.config_dict["supported-zephyr-versions"] - ] + # Next, load the BUILD.py + logging.info("Loading config file %s", path) + code = compile(path.read_bytes(), str(path), "exec") + exec(code, config_globals) + logging.info("Config file %s defines %s projects", path, len(projects)) + return projects - @property - def board(self): - return self.config_dict["board"] - @property - def modules(self): - return self.config_dict.get("modules", list(modules.known_modules)) +def find_projects(root_dir): + """Finds all zmake projects in root_dir. - @property - def output_packer(self): - return packers.packer_registry[self.config_dict["output-type"]] + Args: + root_dir: the root dir as a pathlib.Path object - @property - def supported_toolchains(self): - return self.config_dict["supported-toolchains"] + Returns: + A dictionary mapping project names to Project objects. + """ + logging.info("Finding zmake targets under '%s'.", root_dir) + found_projects = {} + for path in pathlib.Path(root_dir).rglob("BUILD.py"): + for project in load_config_file(path): + if project.config.project_name in found_projects: + raise KeyError( + "Duplicate project defined: {} (in {})".format( + project.config.project_name, path + ) + ) + found_projects[project.config.project_name] = project + return found_projects - @property - def is_test(self): - return self.config_dict.get("is-test", False) - @property - def dts_overlays(self): - return self.config_dict.get("dts-overlays", []) +@dataclasses.dataclass +class ProjectConfig: + project_name: str + zephyr_board: str + supported_toolchains: "list[str]" + output_packer: type + modules: "list[str]" = dataclasses.field( + default_factory=lambda: modules.known_modules, + ) + is_test: bool = dataclasses.field(default=False) + dts_overlays: "list[str]" = dataclasses.field(default_factory=list) + kconfig_files: "list[pathlib.Path]" = dataclasses.field(default_factory=list) + project_dir: pathlib.Path = dataclasses.field(default_factory=pathlib.Path) class Project: """An object encapsulating a project directory.""" - def __init__(self, project_dir, config_dict=None): - self.project_dir = project_dir.resolve() - if not config_dict: - with open(self.project_dir / "zmake.yaml") as f: - config_dict = yaml.safe_load(f) - self.config = ProjectConfig(config_dict) + def __init__(self, config): + self.config = config self.packer = self.config.output_packer(self) def iter_builds(self): @@ -159,10 +113,15 @@ class Project: Yields: 2-tuples of a build configuration name and a BuildConfig. """ - conf = build_config.BuildConfig(cmake_defs={"BOARD": self.config.board}) - prj_conf = self.project_dir / "prj.conf" + conf = build_config.BuildConfig(cmake_defs={"BOARD": self.config.zephyr_board}) + + kconfig_files = [] + prj_conf = self.config.project_dir / "prj.conf" if prj_conf.is_file(): - conf |= build_config.BuildConfig(kconfig_files=[prj_conf]) + kconfig_files.append(prj_conf) + kconfig_files.extend(self.config.kconfig_files) + conf |= build_config.BuildConfig(kconfig_files=kconfig_files) + for build_name, packer_config in self.packer.configs(): yield build_name, conf | packer_config @@ -178,11 +137,11 @@ class Project: """ overlays = [] for module_path in modules.values(): - dts_path = module_dts_overlay_name(module_path, self.config.board) + dts_path = module_dts_overlay_name(module_path, self.config.zephyr_board) if dts_path.is_file(): overlays.append(dts_path.resolve()) - overlays.extend(self.project_dir / f for f in self.config.dts_overlays) + overlays.extend(self.config.dts_overlays) if overlays: return build_config.BuildConfig( @@ -218,7 +177,7 @@ class Project: except KeyError as e: raise KeyError( "The {!r} module is required by the {} project, but is not " - "available.".format(module, self.project_dir) + "available.".format(module, self.config.project_dir) ) from e return result diff --git a/zephyr/zmake/zmake/toolchains.py b/zephyr/zmake/zmake/toolchains.py index 6e25301b7d..671c539c0f 100644 --- a/zephyr/zmake/zmake/toolchains.py +++ b/zephyr/zmake/zmake/toolchains.py @@ -26,7 +26,7 @@ class GenericToolchain: # know if it's installed. Simply return False to indicate not # installed. An unknown toolchain would only be used if -t # was manually passed to zmake, and is not valid to put in a - # zmake.yaml file. + # BUILD.py file. return False def get_build_config(self): @@ -107,7 +107,7 @@ class ZephyrToolchain(GenericToolchain): if not self.zephyr_sdk_install_dir: raise RuntimeError( "No installed Zephyr SDK was found" - " (see docs/zephyr_build.md for documentation)" + " (see docs/zephyr/zephyr_build.md for documentation)" ) tc_vars = { "ZEPHYR_SDK_INSTALL_DIR": str(self.zephyr_sdk_install_dir), diff --git a/zephyr/zmake/zmake/util.py b/zephyr/zmake/zmake/util.py index 0908993267..ee3b245b78 100644 --- a/zephyr/zmake/zmake/util.py +++ b/zephyr/zmake/zmake/util.py @@ -64,19 +64,6 @@ def locate_cros_checkout(): raise FileNotFoundError("Unable to locate a ChromiumOS checkout") -def locate_zephyr_base(zephyr_root, version): - """Locate the path to the Zephyr RTOS in a ChromiumOS checkout. - - Args: - checkout: The path to the ChromiumOS checkout. - version: The requested zephyr version, as a tuple of integers. - - Returns: - The path to the Zephyr source. - """ - return zephyr_root / "v{}.{}".format(*version[:2]) - - def read_kconfig_file(path): """Parse a Kconfig file. @@ -132,23 +119,6 @@ def write_kconfig_file(path, config, only_if_changed=True): f.write("{}={}\n".format(name, value)) -def parse_zephyr_version(version_string): - """Parse a human-readable version string (e.g., "v2.4") as a tuple. - - Args: - version_string: The human-readable version string. - - Returns: - A 2-tuple or 3-tuple of integers representing the version. - """ - match = re.fullmatch(r"v?(\d+)[._](\d+)(?:[._](\d+))?", version_string) - if not match: - raise ValueError( - "{} does not look like a Zephyr version.".format(version_string) - ) - return tuple(int(x) for x in match.groups() if x is not None) - - def read_zephyr_version(zephyr_base): """Read the Zephyr version from a Zephyr OS checkout. @@ -213,36 +183,3 @@ def log_multi_line(logger, level, message): for line in message.splitlines(): if line: logger.log(level, line) - - -def resolve_build_dir(platform_ec_dir, project_dir, build_dir): - """Resolve the build directory using platform/ec/build/... as default. - - Args: - platform_ec_dir: The path to the chromiumos source's platform/ec - directory. - project_dir: The directory of the project. - build_dir: The directory to build in (may be None). - Returns: - The resolved build directory (using build_dir if not None). - """ - if build_dir: - return build_dir - - if not pathlib.Path.exists(project_dir / "zmake.yaml"): - raise OSError("Invalid configuration") - - # Resolve project_dir to absolute path. - project_dir = project_dir.resolve() - - # Compute the path of project_dir relative to platform_ec_dir. - project_relative_path = pathlib.Path.relative_to(project_dir, platform_ec_dir) - - # Make sure that the project_dir is a subdirectory of platform_ec_dir. - if platform_ec_dir / project_relative_path != project_dir: - raise OSError( - "Can't resolve project directory {} which is not a subdirectory" - " of the platform/ec directory {}".format(project_dir, platform_ec_dir) - ) - - return platform_ec_dir / "build" / project_relative_path diff --git a/zephyr/zmake/zmake/version.py b/zephyr/zmake/zmake/version.py index 47aba6d804..b2b897cf5b 100644 --- a/zephyr/zmake/zmake/version.py +++ b/zephyr/zmake/zmake/version.py @@ -90,7 +90,6 @@ def get_version_string(project, zephyr_base, modules, static=False): the build for the OS. """ major_version, minor_version, *_ = util.read_zephyr_version(zephyr_base) - project_id = project.project_dir.parts[-1] num_commits = 0 if static: @@ -116,7 +115,11 @@ def get_version_string(project, zephyr_base, modules, static=False): ) return "{}_v{}.{}.{}-{}".format( - project_id, major_version, minor_version, num_commits, vcs_hashes + project.config.project_name, + major_version, + minor_version, + num_commits, + vcs_hashes, ) diff --git a/zephyr/zmake/zmake/zmake.py b/zephyr/zmake/zmake/zmake.py index 0fa30640c9..d2559a9270 100644 --- a/zephyr/zmake/zmake/zmake.py +++ b/zephyr/zmake/zmake/zmake.py @@ -153,16 +153,17 @@ class Zmake: jobs=0, modules_dir=None, zephyr_base=None, - zephyr_root=None, ): zmake.multiproc.reset() self._checkout = checkout - self._zephyr_base = zephyr_base - if zephyr_root: - self._zephyr_root = zephyr_root + if zephyr_base: + self.zephyr_base = zephyr_base else: - self._zephyr_root = ( - self.checkout / "src" / "third_party" / "zephyr" / "main" + # TODO(b/205884929): Drop v2.7 from path. This is + # intentionally hard-coded here as an intermediate step to + # cutting over to the main branch. + self.zephyr_base = ( + self.checkout / "src" / "third_party" / "zephyr" / "main" / "v2.7" ) if modules_dir: @@ -188,59 +189,62 @@ class Zmake: self._checkout = util.locate_cros_checkout() return self._checkout.resolve() - def locate_zephyr_base(self, version): - """Locate the Zephyr OS repository. - - Args: - version: If a Zephyr OS base was not supplied to Zmake, - which version to search for as a tuple of integers. - This argument is ignored if a Zephyr base was supplied - to Zmake. - Returns: - A pathlib.Path to the found Zephyr OS repository. - """ - if self._zephyr_base: - return self._zephyr_base - - return util.locate_zephyr_base(self._zephyr_root, version) - def configure( self, - project_dir, + project_name_or_dir, build_dir=None, toolchain=None, - ignore_unsupported_zephyr_version=False, build_after_configure=False, test_after_configure=False, bringup=False, coverage=False, + allow_warnings=False, ): - """Set up a build directory to later be built by "zmake build".""" - project = zmake.project.Project(project_dir) - supported_versions = project.config.supported_zephyr_versions - - zephyr_base = self.locate_zephyr_base(max(supported_versions)).resolve() - - # Ignore the patchset from the Zephyr version. - zephyr_version = util.read_zephyr_version(zephyr_base)[:2] - - if ( - not ignore_unsupported_zephyr_version - and zephyr_version not in supported_versions - ): - raise ValueError( - "The Zephyr OS version (v{}.{}) is not supported by the " - "project. You may wish to either configure zmake.yaml to " - "support this version, or pass " - "--ignore-unsupported-zephyr-version.".format(*zephyr_version) - ) - - # Resolve build_dir if needed. - build_dir = util.resolve_build_dir( - platform_ec_dir=self.module_paths["ec"], - project_dir=project_dir, + """Locate a project by name or directory and then call _configure.""" + root_dir = pathlib.Path(project_name_or_dir) + if not root_dir.is_dir(): + root_dir = self.module_paths["ec"] / "zephyr" + found_projects = zmake.project.find_projects(root_dir) + if len(found_projects) == 1: + # Likely passed directory path, wants to build only + # project from there. + project = next(iter(found_projects.values())) + else: + try: + project = found_projects[project_name_or_dir] + except KeyError as e: + raise KeyError("No project named {}".format(project_name_or_dir)) from e + return self._configure( + project=project, build_dir=build_dir, + toolchain=toolchain, + build_after_configure=build_after_configure, + test_after_configure=test_after_configure, + bringup=bringup, + coverage=coverage, + allow_warnings=allow_warnings, ) + + def _configure( + self, + project, + build_dir=None, + toolchain=None, + build_after_configure=False, + test_after_configure=False, + bringup=False, + coverage=False, + allow_warnings=False, + ): + """Set up a build directory to later be built by "zmake build".""" + # Resolve build_dir if needed. + if not build_dir: + build_dir = ( + self.module_paths["ec"] + / "build" + / "zephyr" + / project.config.project_name + ) # Make sure the build directory is clean. if os.path.exists(build_dir): self.logger.info("Clearing old build directory %s", build_dir) @@ -248,8 +252,9 @@ class Zmake: generated_include_dir = (build_dir / "include").resolve() base_config = zmake.build_config.BuildConfig( - environ_defs={"ZEPHYR_BASE": str(zephyr_base), "PATH": "/usr/bin"}, + environ_defs={"ZEPHYR_BASE": str(self.zephyr_base), "PATH": "/usr/bin"}, cmake_defs={ + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "DTS_ROOT": str(self.module_paths["ec"] / "zephyr"), "SYSCALL_INCLUDE_DIRS": str( self.module_paths["ec"] / "zephyr" / "include" / "drivers" @@ -267,7 +272,7 @@ class Zmake: # Symlink the Zephyr base into the build directory so it can # be used in the build phase. - util.update_symlink(zephyr_base, build_dir / "zephyr_base") + util.update_symlink(self.zephyr_base, build_dir / "zephyr_base") dts_overlay_config = project.find_dts_overlays(module_paths) @@ -282,15 +287,21 @@ class Zmake: base_config |= zmake.build_config.BuildConfig( kconfig_defs={"CONFIG_COVERAGE": "y"} ) + if allow_warnings: + base_config |= zmake.build_config.BuildConfig( + cmake_defs={"ALLOW_WARNINGS": "ON"} + ) if not build_dir.exists(): build_dir = build_dir.mkdir() if not generated_include_dir.exists(): generated_include_dir.mkdir() processes = [] - self.logger.info("Building %s in %s.", project_dir, build_dir) + self.logger.info("Building %s in %s.", project.config.project_name, build_dir) for build_name, build_config in project.iter_builds(): - self.logger.info("Configuring %s:%s.", project_dir, build_name) + self.logger.info( + "Configuring %s:%s.", project.config.project_name, build_name + ) config = ( base_config | toolchain_config @@ -302,7 +313,7 @@ class Zmake: kconfig_file = build_dir / "kconfig-{}.conf".format(build_name) proc = config.popen_cmake( self.jobserver, - project_dir, + project.config.project_dir, output_dir, kconfig_file, stdin=subprocess.DEVNULL, @@ -311,7 +322,7 @@ class Zmake: encoding="utf-8", errors="replace", ) - job_id = "{}:{}".format(project_dir, build_name) + job_id = "{}:{}".format(project.config.project_name, build_name) zmake.multiproc.log_output( self.logger, logging.DEBUG, @@ -335,8 +346,10 @@ class Zmake: if proc.wait(): raise OSError(get_process_failure_msg(proc)) - # Create symlink to project - util.update_symlink(project_dir, build_dir / "project") + # To reconstruct a Project object later, we need to know the + # name and project directory. + (build_dir / "project_name.txt").write_text(project.config.project_name) + util.update_symlink(project.config.project_dir, build_dir / "project") if test_after_configure: rv = self.test(build_dir=build_dir) @@ -393,7 +406,8 @@ class Zmake: dirs = {} build_dir = build_dir.resolve() - project = zmake.project.Project(build_dir / "project") + found_projects = zmake.project.find_projects(build_dir / "project") + project = found_projects[(build_dir / "project_name.txt").read_text()] # Compute the version string. version_string = zmake.version.get_version_string( @@ -477,7 +491,8 @@ class Zmake: self.build(build_dir, output_files_out=output_files) # If the project built but isn't a test, just bail. - project = zmake.project.Project(build_dir / "project") + found_projects = zmake.project.find_projects(build_dir / "project") + project = found_projects[(build_dir / "project_name.txt").read_text()] if not project.config.is_test: return 0 @@ -514,17 +529,19 @@ class Zmake: def testall(self): """Test all the valid test targets""" tmp_dirs = [] - for project in zmake.project.find_projects(self.module_paths["ec"] / "zephyr"): + for project in zmake.project.find_projects( + self.module_paths["ec"] / "zephyr" + ).values(): is_test = project.config.is_test temp_build_dir = tempfile.mkdtemp( - suffix="-{}".format(os.path.basename(project.project_dir.as_posix())), + suffix="-{}".format(project.config.project_name), prefix="zbuild-", ) tmp_dirs.append(temp_build_dir) # Configure and run the test. self.executor.append( - func=lambda: self.configure( - project_dir=project.project_dir, + func=lambda: self._configure( + project=project, build_dir=pathlib.Path(temp_build_dir), build_after_configure=True, test_after_configure=is_test, @@ -601,9 +618,9 @@ class Zmake: return 0 def _coverage_compile_only(self, project, build_dir, lcov_file): - self.logger.info("Building %s in %s", project.project_dir, build_dir) - rv = self.configure( - project_dir=project.project_dir, + self.logger.info("Building %s in %s", project.config.project_name, build_dir) + rv = self._configure( + project=project, build_dir=build_dir, build_after_configure=False, test_after_configure=False, @@ -628,7 +645,8 @@ class Zmake: ) # Use ninja to compile the all.libraries target. - build_project = zmake.project.Project(build_dir / "project") + found_projects = zmake.project.find_projects(build_dir / "project") + build_project = found_projects[(build_dir / "project_name.txt").read_text()] procs = [] dirs = {} @@ -679,10 +697,12 @@ class Zmake: lcov_file, is_configured=False, ): - self.logger.info("Running test %s in %s", project.project_dir, build_dir) + self.logger.info( + "Running test %s in %s", project.config.project_name, build_dir + ) if not is_configured: - rv = self.configure( - project_dir=project.project_dir, + rv = self._configure( + project=project, build_dir=build_dir, build_after_configure=True, test_after_configure=True, @@ -699,12 +719,11 @@ class Zmake: """Builds all targets with coverage enabled, and then runs the tests.""" all_lcov_files = [] root_dir = self.module_paths["ec"] / "zephyr" - for project in zmake.project.find_projects(root_dir): + for project in zmake.project.find_projects(root_dir).values(): is_test = project.config.is_test - rel_path = project.project_dir.relative_to(root_dir) - project_build_dir = pathlib.Path(build_dir).joinpath(rel_path) - lcov_file = pathlib.Path(build_dir).joinpath( - str(rel_path).replace("/", "_") + ".info" + project_build_dir = pathlib.Path(build_dir) / project.config.project_name + lcov_file = pathlib.Path(build_dir) / "{}.info".format( + project.config.project_name ) all_lcov_files.append(lcov_file) if is_test: |