From 0de212fab73b66b5c415fd4d36a8e324ca5a1549 Mon Sep 17 00:00:00 2001 From: Rok Mandeljc Date: Tue, 2 Aug 2022 21:42:40 +0200 Subject: [PATCH] building: force the runtime hook execution order based on filenames Force the execution order of runtime hooks that are tied to imported modules and packages by sorting them by their filename (basename). This gives us stable and predictable execution order; up until now, the order depended on the order modules appeared in the modulegraph, i.e., on the order of imports made in the program. It also gives us the ability to force execution of some hooks before other hooks, and conversely, force execution of some hooks after all other hooks, by giving them appropriate names. For example, a runtime hook named `pyi_rth_000_00_mymod_early.py` should be executed first (right after custom runtime hooks), while the one named `pyi_rth_zzz_00_mymod_late.py` should be executed last; regardless of the names used by potential 3rd party runtime hooks, as long as they adhere to the `pyi_rth_` prefix. --- PyInstaller/depend/analysis.py | 51 +++++++++++++++++++--------------- news/7012.feature.rst | 4 +++ 2 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 news/7012.feature.rst diff --git a/PyInstaller/depend/analysis.py b/PyInstaller/depend/analysis.py index 04cc7e0a34..c4db77f691 100644 --- a/PyInstaller/depend/analysis.py +++ b/PyInstaller/depend/analysis.py @@ -594,31 +594,36 @@ def analyze_runtime_hooks(self, custom_runhooks): :return : list of Graph nodes. """ - rthooks_nodes = [] logger.info('Analyzing run-time hooks ...') - # Process custom runtime hooks (from --runtime-hook options). The runtime hooks are order dependent. First hooks - # in the list are executed first. Put their graph nodes at the head of the priority_scripts list Pyinstaller - # defined rthooks and thus they are executed first. - if custom_runhooks: - for hook_file in custom_runhooks: - logger.info("Including custom run-time hook %r", hook_file) - hook_file = os.path.abspath(hook_file) - # Not using "try" here because the path is supposed to exist, if it does not, the raised error will - # explain. - rthooks_nodes.append(self.add_script(hook_file)) - - # Find runtime hooks that are implied by packages already imported. Get a temporary TOC listing all the scripts - # and packages graphed so far. Assuming that runtime hooks apply only to modules and packages. + + # Process custom runtime hooks (from --runtime-hook options). These hooks are executed first, in the same order + # that they are specified. + rthook_nodes_custom = [] + for hook_file in custom_runhooks: + logger.info("Including custom run-time hook %r", hook_file) + hook_file = os.path.abspath(hook_file) + # Not using "try" here because the path is supposed to exist; if not, the error will be self-explanatory. + rthook_nodes_custom.append(self.add_script(hook_file)) + + # Find runtime hooks that are implied by the imported packages/modules. Obtain a temporary TOC listing all + # imported modules. Under current mechanism, the runtime hooks are tied only to modules/packages. + included_rthooks = [] temp_toc = self._make_toc(VALID_MODULE_TYPES) - for (mod_name, path, typecode) in temp_toc: - # Look if there is any run-time hook for given module. - if mod_name in self._available_rthooks: - # There could be several run-time hooks for a module. - for abs_path in self._available_rthooks[mod_name]: - logger.info("Including run-time hook %r", abs_path) - rthooks_nodes.append(self.add_script(abs_path)) - - return rthooks_nodes + for mod_name, path, typecode in temp_toc: + # Process hook(s) for the given module. + for hook_file in self._available_rthooks.get(mod_name, []): + hook_basename = os.path.basename(hook_file) # used for sorting + included_rthooks.append((hook_basename, self.add_script(hook_file))) + + # Sort the included runtime hooks by their basename. This allows predictable ordering, and also allows us to + # influence the order via hook filenames. + included_rthooks = sorted(included_rthooks, key=lambda e: e[0]) + rthook_nodes_regular = [] + for _, hook_node in included_rthooks: + logger.info("Including run-time hook %r", hook_node.filename) + rthook_nodes_regular.append(hook_node) + + return rthook_nodes_custom + rthook_nodes_regular def add_hiddenimports(self, module_list): """ diff --git a/news/7012.feature.rst b/news/7012.feature.rst new file mode 100644 index 0000000000..d90c2b4d85 --- /dev/null +++ b/news/7012.feature.rst @@ -0,0 +1,4 @@ +Make runtime hook execution order deterministic, based on the runtime +hook filenames. This change affects only the runtime hooks tied to +the imported modules; the custom runtime hooks are still executed +first and in the order that they are specified.