diff options
Diffstat (limited to 'zephyr/zmake/zmake/zmake.py')
-rw-r--r-- | zephyr/zmake/zmake/zmake.py | 691 |
1 files changed, 348 insertions, 343 deletions
diff --git a/zephyr/zmake/zmake/zmake.py b/zephyr/zmake/zmake/zmake.py index fb88dae7e9..f81f157054 100644 --- a/zephyr/zmake/zmake/zmake.py +++ b/zephyr/zmake/zmake/zmake.py @@ -187,10 +187,13 @@ class Zmake: if jobserver: self.jobserver = jobserver else: - self.jobserver = zmake.jobserver.GNUMakeJobServer(jobs=jobs) + try: + self.jobserver = zmake.jobserver.GNUMakeJobClient.from_environ() + except OSError: + self.jobserver = zmake.jobserver.GNUMakeJobServer(jobs=jobs) self.executor = zmake.multiproc.Executor() - self._sequential = self.jobserver.is_sequential() and not goma + self._sequential = jobs == 1 and not goma self.failed_projects = [] @property @@ -240,7 +243,6 @@ class Zmake: delete_intermediates=False, static_version=False, save_temps=False, - wait_for_executor=True, ): """Locate and configure the specified projects.""" # Resolve build_dir if needed. @@ -267,6 +269,7 @@ class Zmake: coverage=coverage, allow_warnings=allow_warnings, extra_cflags=extra_cflags, + multiproject=len(projects) > 1, delete_intermediates=delete_intermediates, static_version=static_version, save_temps=save_temps, @@ -276,11 +279,11 @@ class Zmake: result = self.executor.wait() if result: return result + result = self.executor.wait() + if result: + return result non_test_projects = [p for p in projects if not p.config.is_test] if len(non_test_projects) > 1 and coverage and build_after_configure: - result = self.executor.wait() - if result: - return result result = self._merge_lcov_files( projects=non_test_projects, build_dir=build_dir, @@ -289,11 +292,6 @@ class Zmake: if result: self.failed_projects.append(str(build_dir / "all_builds.info")) return result - elif wait_for_executor: - result = self.executor.wait() - if result: - return result - return 0 def build( @@ -387,10 +385,8 @@ class Zmake: delete_intermediates=False, static_version=True, save_temps=False, - wait_for_executor=False, ) - if not result: - result = self.executor.wait() + if result: self.logger.error( "compare-builds failed to build all projects at %s", @@ -444,364 +440,372 @@ class Zmake: coverage=False, allow_warnings=False, extra_cflags=None, + multiproject=False, delete_intermediates=False, static_version=False, save_temps=False, ): """Set up a build directory to later be built by "zmake build".""" try: - with self.jobserver.get_job(): - # 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) + # 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() + base_config = zmake.build_config.BuildConfig( + 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" + ), + "USER_CACHE_DIR": str( + self.module_paths["ec"] + / "build" + / "zephyr" + / "user-cache" + ), + "ZEPHYR_BASE": str(self.zephyr_base), + "ZMAKE_INCLUDE_DIR": str(generated_include_dir), + "ZMAKE_PROJECT_NAME": project.config.project_name, + **( + {"EXTRA_EC_VERSION_FLAGS": "--static"} + if static_version + else {} + ), + **( + {"EXTRA_CFLAGS": "-save-temps=obj"} + if save_temps + else {} + ), + }, + ) + + # Prune the module paths to just those required by the project. + module_paths = project.prune_modules(self.module_paths) + + module_config = zmake.modules.setup_module_symlinks( + build_dir / "modules", module_paths + ) + + # Symlink the Zephyr base into the build directory so it can + # be used in the build phase. + util.update_symlink(self.zephyr_base, build_dir / "zephyr_base") + + dts_overlay_config = project.find_dts_overlays(module_paths) - generated_include_dir = (build_dir / "include").resolve() - base_config = zmake.build_config.BuildConfig( + toolchain_support = project.get_toolchain( + module_paths, override=toolchain + ) + toolchain_config = toolchain_support.get_build_config() + + if bringup: + base_config |= zmake.build_config.BuildConfig( + kconfig_defs={"CONFIG_PLATFORM_EC_BRINGUP": "y"} + ) + if coverage: + 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 extra_cflags: + base_config |= zmake.build_config.BuildConfig( + cmake_defs={"EXTRA_CFLAGS": extra_cflags}, + ) + if self.goma: + base_config |= zmake.build_config.BuildConfig( 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" - ), - "USER_CACHE_DIR": str( - self.module_paths["ec"] - / "build" - / "zephyr" - / "user-cache" - ), - "ZEPHYR_BASE": str(self.zephyr_base), - "ZMAKE_INCLUDE_DIR": str(generated_include_dir), - "ZMAKE_PROJECT_NAME": project.config.project_name, - **( - {"EXTRA_EC_VERSION_FLAGS": "--static"} - if static_version - else {} - ), - **( - {"EXTRA_CFLAGS": "-save-temps=obj"} - if save_temps - else {} - ), + "CMAKE_C_COMPILER_LAUNCHER": self.gomacc, + "CMAKE_CXX_COMPILER_LAUNCHER": self.gomacc, }, ) - # Prune the module paths to just those required by the project. - module_paths = project.prune_modules(self.module_paths) - - module_config = zmake.modules.setup_module_symlinks( - build_dir / "modules", module_paths + if not build_dir.exists(): + build_dir.mkdir() + 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(): + config: zmake.build_config.BuildConfig = ( + base_config + | toolchain_config + | module_config + | dts_overlay_config + | build_config ) - # Symlink the Zephyr base into the build directory so it can - # be used in the build phase. - util.update_symlink(self.zephyr_base, build_dir / "zephyr_base") - - dts_overlay_config = project.find_dts_overlays(module_paths) + 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 + config_json_file.unlink() - toolchain_support = project.get_toolchain( - module_paths, override=toolchain - ) - toolchain_config = toolchain_support.get_build_config() + files_to_write.append((config_json_file, config_json)) - if bringup: - base_config |= zmake.build_config.BuildConfig( - kconfig_defs={"CONFIG_PLATFORM_EC_BRINGUP": "y"} - ) - if coverage: - 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 extra_cflags: - base_config |= zmake.build_config.BuildConfig( - cmake_defs={"EXTRA_CFLAGS": extra_cflags}, - ) - if self.goma: - base_config |= zmake.build_config.BuildConfig( - cmake_defs={ - "CMAKE_C_COMPILER_LAUNCHER": self.gomacc, - "CMAKE_CXX_COMPILER_LAUNCHER": self.gomacc, - }, + 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) - if not build_dir.exists(): - build_dir.mkdir() - if not generated_include_dir.exists(): - generated_include_dir.mkdir() self.logger.info( - "Building %s in %s.", project.config.project_name, build_dir + "Configuring %s:%s.", + project.config.project_name, + build_name, ) - # 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 + + kconfig_file = build_dir / "kconfig-{}.conf".format(build_name) + proc = config.popen_cmake( + self.jobserver, + project.config.project_dir, + output_dir, + kconfig_file, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + errors="replace", ) - util.update_symlink( - project.config.project_dir, build_dir / "project" + job_id = "{}:{}".format(project.config.project_name, build_name) + zmake.multiproc.LogWriter.log_output( + self.logger, + logging.DEBUG, + proc.stdout, + log_level_override_func=cmake_log_level_override, + job_id=job_id, ) + zmake.multiproc.LogWriter.log_output( + self.logger, + logging.ERROR, + proc.stderr, + log_level_override_func=cmake_log_level_override, + job_id=job_id, + ) + if self._sequential: + if proc.wait(): + raise OSError(get_process_failure_msg(proc)) + else: + processes.append(proc) + for proc in processes: + 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 + ) + util.update_symlink( + project.config.project_dir, build_dir / "project" + ) - wait_funcs = [] - for build_name, build_config in project.iter_builds(): - config: zmake.build_config.BuildConfig = ( - base_config - | toolchain_config - | module_config - | dts_overlay_config - | build_config - ) - - wait_func = self.executor.append( - func=functools.partial( - self._configure_one_build, - config=config, - build_dir=build_dir, - build_name=build_name, - project=project, - ) - ) - wait_funcs.append(wait_func) - # Outside the with...get_job above. - for wait_func in wait_funcs: - wait_func() - + output_files = [] if build_after_configure: - self._build( + result = self._build( build_dir=build_dir, project=project, coverage=coverage, + output_files_out=output_files, + multiproject=multiproject, static_version=static_version, - delete_intermediates=delete_intermediates, ) + if result: + self.failed_projects.append(project.config.project_name) + return result + + if delete_intermediates: + outdir = build_dir / "output" + for child in build_dir.iterdir(): + if child != outdir: + logging.debug("Deleting %s", child) + if not child.is_symlink() and child.is_dir(): + shutil.rmtree(child) + else: + child.unlink() return 0 except Exception: self.failed_projects.append(project.config.project_name) raise - def _configure_one_build( - self, - config, - build_dir, - build_name, - project, - ): - """Run cmake and maybe ninja on one build dir.""" - with self.jobserver.get_job(): - 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, - ) - return 0 - config_json_file.unlink() - - 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, - project.config.project_dir, - output_dir, - kconfig_file, - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf-8", - errors="replace", - ) - job_id = "{}:{}".format(project.config.project_name, build_name) - zmake.multiproc.LogWriter.log_output( - self.logger, - logging.DEBUG, - proc.stdout, - log_level_override_func=cmake_log_level_override, - job_id=job_id, - ) - zmake.multiproc.LogWriter.log_output( - self.logger, - logging.ERROR, - proc.stderr, - log_level_override_func=cmake_log_level_override, - job_id=job_id, - ) - if proc.wait(): - raise OSError(get_process_failure_msg(proc)) - config_json_file.write_text(config_json) - return 0 - def _build( self, build_dir, project: zmake.project.Project, + output_files_out=None, coverage=False, + multiproject=False, static_version=False, - delete_intermediates=False, ): """Build a pre-configured build directory.""" - with self.jobserver.get_job(): - dirs: Dict[str, pathlib.Path] = {} - - build_dir = build_dir.resolve() + def wait_and_check_success(procs, writers): + """Wait for processes to complete and check for errors + + Args: + procs: List of subprocess.Popen objects to check + writers: List of LogWriter objects to check + + Returns: + True if all if OK + False if an error was found (so that zmake should exit) + """ + bad = None + for proc in procs: + if proc.wait() and not bad: + bad = proc + if bad: + # Just show the first bad process for now. Both builds likely + # produce the same error anyway. If they don't, the user can + # still take action on the errors/warnings provided. Showing + # multiple 'Execution failed' messages is not very friendly + # since it exposes the fragmented nature of the build. + raise OSError(get_process_failure_msg(bad)) - # Compute the version string. - version_string = zmake.version.get_version_string( - project.config.project_name, - build_dir / "zephyr_base", - zmake.modules.locate_from_directory(build_dir / "modules"), - static=static_version, - ) + # Let all output be produced before exiting + for writer in writers: + writer.wait() + return True + + procs = [] + log_writers = [] + dirs: Dict[str, pathlib.Path] = {} + + build_dir = build_dir.resolve() + + # Compute the version string. + version_string = zmake.version.get_version_string( + project.config.project_name, + build_dir / "zephyr_base", + zmake.modules.locate_from_directory(build_dir / "modules"), + static=static_version, + ) - # The version header needs to generated during the build phase - # instead of configure, as the tree may have changed since - # configure was run. - zmake.version.write_version_header( - version_string, - build_dir / "include" / "ec_version.h", - "zmake", - static=static_version, - ) + # The version header needs to generated during the build phase + # instead of configure, as the tree may have changed since + # configure was run. + zmake.version.write_version_header( + version_string, + build_dir / "include" / "ec_version.h", + "zmake", + static=static_version, + ) - gcov = "gcov.sh-not-found" - wait_funcs = [] - for build_name, _ in project.iter_builds(): + gcov = "gcov.sh-not-found" + for build_name, _ in project.iter_builds(): + with self.jobserver.get_job(): dirs[build_name] = build_dir / "build-{}".format(build_name) gcov = dirs[build_name] / "gcov.sh" - wait_func = self.executor.append( - func=functools.partial( - self._build_one_dir, - build_name=build_name, - dirs=dirs, - coverage=coverage, - project=project, - ) + cmd = ["/usr/bin/ninja", "-C", dirs[build_name].as_posix()] + if self.goma: + # Go nuts ninja, goma does the heavy lifting! + cmd.append("-j1024") + elif multiproject: + cmd.append("-j1") + # Only tests will actually build with coverage enabled. + if coverage and not project.config.is_test: + cmd.append("all.libraries") + self.logger.info( + "Building %s:%s: %s", + project.config.project_name, + build_name, + util.repr_command(cmd), + ) + proc = self.jobserver.popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + errors="replace", + # TODO(b/239619222): Filter os.environ for ninja. + env=os.environ, + ) + job_id = "{}:{}".format(project.config.project_name, build_name) + dirs[build_name].mkdir(parents=True, exist_ok=True) + build_log = open( # pylint:disable=consider-using-with + dirs[build_name] / "build.log", + "w", + ) + out = zmake.multiproc.LogWriter.log_output( + logger=self.logger, + log_level=logging.INFO, + file_descriptor=proc.stdout, + log_level_override_func=ninja_stdout_log_level_override, + job_id=job_id, + tee_output=build_log, + ) + err = zmake.multiproc.LogWriter.log_output( + self.logger, + logging.ERROR, + proc.stderr, + job_id=job_id, ) - wait_funcs.append(wait_func) - # Outside the with...get_job above. - for wait_func in wait_funcs: - wait_func() - with self.jobserver.get_job(): - # Run the packer. - packer_work_dir = build_dir / "packer" - output_dir = build_dir / "output" - for newdir in output_dir, packer_work_dir: - if not newdir.exists(): - newdir.mkdir() - - # For non-tests, they won't link with coverage, so don't pack the - # firmware. Also generate a lcov file. - if coverage and not project.config.is_test: + if self._sequential: + if not wait_and_check_success([proc], [out, err]): + return 2 + else: + procs.append(proc) + log_writers += [out, err] + + if not wait_and_check_success(procs, log_writers): + return 2 + + # Run the packer. + packer_work_dir = build_dir / "packer" + output_dir = build_dir / "output" + for newdir in output_dir, packer_work_dir: + if not newdir.exists(): + newdir.mkdir() + + if output_files_out is None: + output_files_out = [] + # For non-tests, they won't link with coverage, so don't pack the + # firmware. Also generate a lcov file. + if coverage and not project.config.is_test: + with self.jobserver.get_job(): self._run_lcov( build_dir, output_dir / "zephyr.info", initial=True, gcov=gcov, ) - else: - for output_file, output_name in project.packer.pack_firmware( - packer_work_dir, - self.jobserver, - dirs, - version_string=version_string, - ): - shutil.copy2(output_file, output_dir / output_name) - self.logger.debug("Output file '%s' created.", output_file) - - if delete_intermediates: - outdir = build_dir / "output" - for child in build_dir.iterdir(): - if child != outdir: - logging.debug("Deleting %s", child) - if not child.is_symlink() and child.is_dir(): - shutil.rmtree(child) - else: - child.unlink() - return 0 - - def _build_one_dir(self, build_name, dirs, coverage, project): - """Builds one sub-dir of a configured project (build-ro, etc).""" - - with self.jobserver.get_job(): - cmd = ["/usr/bin/ninja", "-C", dirs[build_name].as_posix()] - if self.goma: - # Go nuts ninja, goma does the heavy lifting! - cmd.append("-j1024") - elif self._sequential: - cmd.append("-j1") - # Only tests will actually build with coverage enabled. - if coverage and not project.config.is_test: - cmd.append("all.libraries") - self.logger.info( - "Building %s:%s: %s", - project.config.project_name, - build_name, - util.repr_command(cmd), - ) - proc = self.jobserver.popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf-8", - errors="replace", - # TODO(b/239619222): Filter os.environ for ninja. - env=os.environ, - ) - job_id = "{}:{}".format(project.config.project_name, build_name) - dirs[build_name].mkdir(parents=True, exist_ok=True) - build_log = open( # pylint:disable=consider-using-with - dirs[build_name] / "build.log", - "w", - ) - out = zmake.multiproc.LogWriter.log_output( - logger=self.logger, - log_level=logging.INFO, - file_descriptor=proc.stdout, - log_level_override_func=ninja_stdout_log_level_override, - job_id=job_id, - tee_output=build_log, - ) - err = zmake.multiproc.LogWriter.log_output( - self.logger, - logging.ERROR, - proc.stderr, - job_id=job_id, - ) - - if proc.wait(): - raise OSError(get_process_failure_msg(proc)) + else: + for output_file, output_name in project.packer.pack_firmware( + packer_work_dir, + self.jobserver, + dirs, + version_string=version_string, + ): + shutil.copy2(output_file, output_dir / output_name) + self.logger.debug("Output file '%s' created.", output_file) + output_files_out.append(output_file) - # Let all output be produced before exiting - out.wait() - err.wait() - return 0 + return 0 def _run_lcov( self, @@ -865,33 +869,34 @@ class Zmake: pathlib.Path(build_dir) / project.config.project_name ) all_lcov_files.append(project_build_dir / "output" / "zephyr.info") - # Merge info files into a single lcov.info - self.logger.info("Merging coverage data into %s.", output_file) - cmd = [ - "/usr/bin/lcov", - "-o", - output_file, - "--rc", - "lcov_branch_coverage=1", - ] - for info in all_lcov_files: - cmd += ["-a", info] - proc = self.jobserver.popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf-8", - errors="replace", - ) - zmake.multiproc.LogWriter.log_output( - self.logger, logging.ERROR, proc.stderr, job_id="lcov" - ) - zmake.multiproc.LogWriter.log_output( - self.logger, logging.DEBUG, proc.stdout, job_id="lcov" - ) - if proc.wait(): - raise OSError(get_process_failure_msg(proc)) - return 0 + with self.jobserver.get_job(): + # Merge info files into a single lcov.info + self.logger.info("Merging coverage data into %s.", output_file) + cmd = [ + "/usr/bin/lcov", + "-o", + output_file, + "--rc", + "lcov_branch_coverage=1", + ] + for info in all_lcov_files: + cmd += ["-a", info] + proc = self.jobserver.popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + errors="replace", + ) + zmake.multiproc.LogWriter.log_output( + self.logger, logging.ERROR, proc.stderr, job_id="lcov" + ) + zmake.multiproc.LogWriter.log_output( + self.logger, logging.DEBUG, proc.stdout, job_id="lcov" + ) + if proc.wait(): + raise OSError(get_process_failure_msg(proc)) + return 0 def list_projects(self, fmt, search_dir): """List project names known to zmake on stdout. |