diff options
author | Jack Rosenthal <jrosenth@chromium.org> | 2022-01-12 20:48:45 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2022-01-14 03:06:24 +0000 |
commit | 48e4bc44135f9d4ac0f73c6b502f09533b564b4d (patch) | |
tree | 5124a54548b89618adc930753f72bb1fa1c355a3 | |
parent | 1465b300ead05866395445e83c5049392b9adac5 (diff) | |
download | chrome-ec-48e4bc44135f9d4ac0f73c6b502f09533b564b4d.tar.gz |
zephyr: zmake: Only re-run cmake when required
Prior to this commit, when `zmake configure` was run on an existing
build directory, the build directory would always clobber. Since most
people are building by using "zmake configure -b ${PROJECT_NAME}" each
time, this means that we almost never can do incremental builds.
Most of the time we can actually save the existing cmake configuration
when running configure however, but we do have to be careful to not
save it too often: a change of configuration options such as the
toolchain could mean that the build directory really does need
clobbered.
This CL adds logic to clobber build directories only when necessary by
an actual configuration difference. We do so by serializing the build
configuration into a JSON file at ${BUILD_DIR}/cfg-{ro,rw}.json after
a successful run of cmake. Then, before running cmake each time, if
this JSON file exists and has the same build configuration we're about
to run cmake for, we simply skip running cmake entirely.
BUG=b:214321770
BRANCH=none
TEST="zmake configure -b volteer", then do it again. Second run only
recompiles version file and then links.
Change-Id: Iee7e6a15f36af9dc398a8a795884bf42b1a38833
Signed-off-by: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3384403
Reviewed-by: Jeremy Bettis <jbettis@chromium.org>
Reviewed-by: Keith Short <keithshort@chromium.org>
-rw-r--r-- | zephyr/zmake/README.md | 6 | ||||
-rw-r--r-- | zephyr/zmake/tests/test_build_config.py | 54 | ||||
-rw-r--r-- | zephyr/zmake/tests/test_zmake.py | 4 | ||||
-rw-r--r-- | zephyr/zmake/zmake/__main__.py | 14 | ||||
-rw-r--r-- | zephyr/zmake/zmake/build_config.py | 13 | ||||
-rw-r--r-- | zephyr/zmake/zmake/zmake.py | 46 |
6 files changed, 126 insertions, 11 deletions
diff --git a/zephyr/zmake/README.md b/zephyr/zmake/README.md index 986cc8a506..d3f99fe753 100644 --- a/zephyr/zmake/README.md +++ b/zephyr/zmake/README.md @@ -34,7 +34,7 @@ Chromium OS's meta-build tool for Zephyr ### zmake configure -**Usage:** `zmake configure [-h] [-t TOOLCHAIN] [--bringup] [--allow-warnings] [-B BUILD_DIR] [-b] [--test] project_name_or_dir [-c]` +**Usage:** `zmake configure [-h] [-t TOOLCHAIN] [--bringup] [--clobber] [--allow-warnings] [-B BUILD_DIR] [-b] [--test] project_name_or_dir [-c]` #### Positional Arguments @@ -49,6 +49,7 @@ Chromium OS's meta-build tool for Zephyr | `-h`, `--help` | show this help message and exit | | `-t TOOLCHAIN`, `--toolchain TOOLCHAIN` | Name of toolchain to use | | `--bringup` | Enable bringup debugging features | +| `--clobber` | Delete existing build directories, even if configuration is unchanged | | `--allow-warnings` | Do not treat warnings as errors | | `-B BUILD_DIR`, `--build-dir BUILD_DIR` | Build directory | | `-b`, `--build` | Run the build after configuration | @@ -107,13 +108,14 @@ Chromium OS's meta-build tool for Zephyr ### zmake testall -**Usage:** `zmake testall [-h]` +**Usage:** `zmake testall [-h] [--clobber]` #### Optional Arguments | | | |---|---| | `-h`, `--help` | show this help message and exit | +| `--clobber` | Delete existing build directories, even if configuration is unchanged | ### zmake coverage diff --git a/zephyr/zmake/tests/test_build_config.py b/zephyr/zmake/tests/test_build_config.py index bf69b8a1fa..ba81bd480d 100644 --- a/zephyr/zmake/tests/test_build_config.py +++ b/zephyr/zmake/tests/test_build_config.py @@ -200,3 +200,57 @@ def test_popen_cmake_kconfig(conf, project_dir, build_dir): assert kconfig_defs == conf.kconfig_defs finally: os.unlink(temp_path) + + +def test_build_config_json_stability(): + # as_json() should return equivalent strings for two equivalent + # build configs. + a = BuildConfig( + environ_defs={ + "A": "B", + "B": "C", + }, + cmake_defs={ + "Z": "Y", + "X": "W", + }, + kconfig_defs={ + "CONFIG_A": "y", + "CONFIG_B": "n", + }, + kconfig_files=[ + pathlib.Path("/a/b/c.conf"), + pathlib.Path("d/e/f.conf"), + ], + ) + + # Dict ordering is intentionally reversed in b. + b = BuildConfig( + environ_defs={ + "B": "C", + "A": "B", + }, + cmake_defs={ + "X": "W", + "Z": "Y", + }, + kconfig_defs={ + "CONFIG_B": "n", + "CONFIG_A": "y", + }, + kconfig_files=[ + pathlib.Path("/a/b/c.conf"), + pathlib.Path("d/e/f.conf"), + ], + ) + + assert a.as_json() == b.as_json() + + +def test_build_config_json_inequality(): + # Two differing build configs should not have the same json + # representation. + a = BuildConfig(cmake_defs={"A": "B"}) + b = BuildConfig(environ_defs={"A": "B"}) + + assert a.as_json() != b.as_json() diff --git a/zephyr/zmake/tests/test_zmake.py b/zephyr/zmake/tests/test_zmake.py index 83862ce65d..f2759e5af3 100644 --- a/zephyr/zmake/tests/test_zmake.py +++ b/zephyr/zmake/tests/test_zmake.py @@ -155,7 +155,9 @@ EXTRAVERSION = return_value={"fakeproject": FakeProject()}, ): if use_configure: - zmk.configure("fakeproject", build_dir=pathlib.Path("build")) + zmk.configure( + "fakeproject", build_dir=pathlib.Path("build"), clobber=True + ) 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 28ec232148..d8f33a97d9 100644 --- a/zephyr/zmake/zmake/__main__.py +++ b/zephyr/zmake/zmake/__main__.py @@ -176,6 +176,12 @@ def get_argparser(): help="Enable bringup debugging features", ) configure.add_argument( + "--clobber", + action="store_true", + dest="clobber", + help="Delete existing build directories, even if configuration is unchanged", + ) + configure.add_argument( "--allow-warnings", action="store_true", default=False, @@ -254,10 +260,16 @@ def get_argparser(): help="The build directory used during configuration", ) - sub.add_parser( + testall = sub.add_parser( "testall", help="Execute all known builds and tests", ) + testall.add_argument( + "--clobber", + action="store_true", + dest="clobber", + help="Delete existing build directories, even if configuration is unchanged", + ) coverage = sub.add_parser( "coverage", diff --git a/zephyr/zmake/zmake/build_config.py b/zephyr/zmake/zmake/build_config.py index 9a9c7f36a2..85e06d8f11 100644 --- a/zephyr/zmake/zmake/build_config.py +++ b/zephyr/zmake/zmake/build_config.py @@ -3,6 +3,7 @@ # found in the LICENSE file. """Encapsulation of a build configuration.""" +import json import zmake.util as util @@ -98,3 +99,15 @@ class BuildConfig: if getattr(self, name) ) ) + + def as_json(self): + """Provide a stable JSON representation of the build config.""" + return json.dumps( + { + "environ_defs": self.environ_defs, + "cmake_defs": self.cmake_defs, + "kconfig_defs": self.kconfig_defs, + "kconfig_files": [str(p.resolve()) for p in self.kconfig_files], + }, + sort_keys=True, + ) diff --git a/zephyr/zmake/zmake/zmake.py b/zephyr/zmake/zmake/zmake.py index abc41de855..47a79f179b 100644 --- a/zephyr/zmake/zmake/zmake.py +++ b/zephyr/zmake/zmake/zmake.py @@ -193,6 +193,7 @@ class Zmake: toolchain=None, build_after_configure=False, test_after_configure=False, + clobber=False, bringup=False, coverage=False, allow_warnings=False, @@ -217,6 +218,7 @@ class Zmake: toolchain=toolchain, build_after_configure=build_after_configure, test_after_configure=test_after_configure, + clobber=clobber, bringup=bringup, coverage=coverage, allow_warnings=allow_warnings, @@ -229,6 +231,7 @@ class Zmake: toolchain=None, build_after_configure=False, test_after_configure=False, + clobber=False, bringup=False, coverage=False, allow_warnings=False, @@ -242,9 +245,10 @@ class Zmake: / "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) + + # Clobber build directory if requested. + if clobber and build_dir.exists(): + self.logger.info("Clearing build directory %s due to --clobber", build_dir) shutil.rmtree(build_dir) generated_include_dir = (build_dir / "include").resolve() @@ -294,11 +298,9 @@ class Zmake: if not generated_include_dir.exists(): generated_include_dir.mkdir() processes = [] + files_to_write = [] 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.config.project_name, build_name - ) config = ( base_config | toolchain_config @@ -306,7 +308,33 @@ class Zmake: | dts_overlay_config | build_config ) + + config_json = config.as_json() + config_json_file = build_dir / f"cfg-{build_name}.json" + if config_json_file.is_file(): + if config_json_file.read_text() == config_json: + self.logger.info( + "Skip reconfiguring %s:%s due to previous cmake run of " + "equivalent configuration. Run with --clobber if this " + "optimization is undesired.", + project.config.project_name, + build_name, + ) + continue + else: + config_json_file.unlink() + + files_to_write.append((config_json_file, config_json)) + output_dir = build_dir / "build-{}".format(build_name) + if output_dir.exists(): + self.logger.info("Clobber %s due to configuration changes.", output_dir) + shutil.rmtree(output_dir) + + self.logger.info( + "Configuring %s:%s.", project.config.project_name, build_name + ) + kconfig_file = build_dir / "kconfig-{}.conf".format(build_name) proc = config.popen_cmake( self.jobserver, @@ -343,6 +371,9 @@ class Zmake: if proc.wait(): raise OSError(get_process_failure_msg(proc)) + for path, contents in files_to_write: + path.write_text(contents) + # 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) @@ -531,7 +562,7 @@ class Zmake: raise OSError(get_process_failure_msg(proc)) return 0 - def testall(self): + def testall(self, clobber=False): """Test all the valid test targets""" tmp_dirs = [] for project in zmake.project.find_projects( @@ -550,6 +581,7 @@ class Zmake: build_dir=pathlib.Path(temp_build_dir), build_after_configure=True, test_after_configure=is_test, + clobber=clobber, ) ) |