Repository: conan-io/conan

Issue:
Hooks: post_package_info
To receive the adjusted `conanfile.cpp_info` and perform some checks.


Discussion:

Resolving patch:
diff --git a/conans/client/hook_manager.py b/conans/client/hook_manager.py
--- a/conans/client/hook_manager.py
+++ b/conans/client/hook_manager.py
@@ -28,7 +28,8 @@ def pre_export(output, conanfile, conanfile_path, reference, **kwargs):
                       "pre_upload_package", "post_upload_package",
                       "pre_download", "post_download",
                       "pre_download_recipe", "post_download_recipe",
-                      "pre_download_package", "post_download_package"]
+                      "pre_download_package", "post_download_package",
+                      "pre_package_info", "post_package_info"]
 
 
 class HookManager(object):
@@ -49,7 +50,8 @@ def execute(self, method_name, **kwargs):
         if not self.hooks:
             self.load_hooks()
 
-        assert method_name in valid_hook_methods
+        assert method_name in valid_hook_methods, \
+            "Method '{}' not in valid hooks methods".format(method_name)
         for name, method in self.hooks[method_name]:
             try:
                 output = ScopedOutput("[HOOK - %s] %s()" % (name, method_name), self.output)
diff --git a/conans/client/installer.py b/conans/client/installer.py
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -343,7 +343,7 @@ def _handle_node_editable(self, node, graph_info):
         # Get source of information
         package_layout = self._cache.package_layout(node.ref)
         base_path = package_layout.base_folder()
-        self._call_package_info(node.conanfile, package_folder=base_path)
+        self._call_package_info(node.conanfile, package_folder=base_path, ref=node.ref)
 
         node.conanfile.cpp_info.filter_empty = False
         # Try with package-provided file
@@ -376,9 +376,9 @@ def _handle_node_cache(self, node, keep_build, processed_package_references, rem
         pref = node.pref
         assert pref.id, "Package-ID without value"
 
-        conan_file = node.conanfile
-        output = conan_file.output
-        package_folder = self._cache.package_layout(pref.ref, conan_file.short_paths).package(pref)
+        conanfile = node.conanfile
+        output = conanfile.output
+        package_folder = self._cache.package_layout(pref.ref, conanfile.short_paths).package(pref)
 
         with self._cache.package_layout(pref.ref).package_lock(pref):
             if pref not in processed_package_references:
@@ -413,8 +413,8 @@ def _handle_node_cache(self, node, keep_build, processed_package_references, rem
                     self._recorder.package_fetched_from_cache(pref)
 
             # Call the info method
-            self._call_package_info(conan_file, package_folder)
-            self._recorder.package_cpp_info(pref, conan_file.cpp_info)
+            self._call_package_info(conanfile, package_folder, ref=pref.ref)
+            self._recorder.package_cpp_info(pref, conanfile.cpp_info)
 
     def _build_package(self, node, pref, output, keep_build, remotes):
         conanfile = node.conanfile
@@ -453,8 +453,7 @@ def _propagate_info(node):
                    package_name == conan_file.name:
                     conan_file.info.env_values.add(name, value, package_name)
 
-    @staticmethod
-    def _call_package_info(conanfile, package_folder):
+    def _call_package_info(self, conanfile, package_folder, ref):
         conanfile.cpp_info = CppInfo(package_folder)
         conanfile.cpp_info.version = conanfile.version
         conanfile.cpp_info.description = conanfile.description
@@ -473,4 +472,8 @@ def _call_package_info(conanfile, package_folder):
                     conanfile.source_folder = None
                     conanfile.build_folder = None
                     conanfile.install_folder = None
+                    self._hook_manager.execute("pre_package_info", conanfile=conanfile,
+                                               reference=ref)
                     conanfile.package_info()
+                    self._hook_manager.execute("post_package_info", conanfile=conanfile,
+                                               reference=ref)


Test patch:
diff --git a/conans/test/integration/hook_test.py b/conans/test/integration/hook_test.py
--- a/conans/test/integration/hook_test.py
+++ b/conans/test/integration/hook_test.py
@@ -10,6 +10,9 @@
 class AConan(ConanFile):
     name = "basic"
     version = "0.1"
+    
+    def package_info(self):
+        self.cpp_info.defines = ["ACONAN"]
 """
 
 complete_hook = """
@@ -129,6 +132,14 @@ def post_download_package(output, conanfile_path, reference, package_id, remote,
     output.info("reference=%s" % reference.full_repr())
     output.info("package_id=%s" % package_id)
     output.info("remote.name=%s" % remote.name)
+    
+def pre_package_info(output, conanfile, reference, **kwargs):
+    output.info("reference=%s" % reference.full_repr())
+    output.info("conanfile.cpp_info.defines=%s" % conanfile.cpp_info.defines)
+
+def post_package_info(output, conanfile, reference, **kwargs):
+    output.info("reference=%s" % reference.full_repr())
+    output.info("conanfile.cpp_info.defines=%s" % conanfile.cpp_info.defines)
 """
 
 HEADER = "[HOOK - complete_hook] {method_name}():"
@@ -202,6 +213,7 @@ def complete_hook_test(self):
         self._check_source(conanfile_cache_path, client.out, in_cache=True)
         self._check_build(conanfile_cache_path, client.out, in_cache=True)
         self._check_package(conanfile_cache_path, client.out, in_cache=True)
+        self._check_package_info(client.out)
 
         client.run("upload basic/0.1@danimtb/testing -r default")
         self._check_upload(conanfile_cache_path, client.out)
@@ -225,6 +237,7 @@ def complete_hook_test(self):
         client.run("install basic/0.1@danimtb/testing")
         self._check_download_recipe(conanfile_cache_path, client.out)
         self._check_download_package(conanfile_cache_path, client.out)
+        self._check_package_info(client.out)
 
     def _check_source(self, conanfile_path, out, in_cache=False):
         reference = REFERENCE_CACHE if in_cache else REFERENCE_LOCAL
@@ -383,6 +396,16 @@ def _check_download_package(self, conanfile_cache_path, out):
         self.assertIn("[HOOK - complete_hook/complete_hook.py] post_download_package(): "
                       "remote.name=default", out)
 
+    def _check_package_info(self, out):
+        self.assertIn("[HOOK - complete_hook/complete_hook.py] pre_package_info(): "
+                      "reference=%s" % REFERENCE_CACHE, out)
+        self.assertIn("[HOOK - complete_hook/complete_hook.py] pre_package_info(): "
+                      "conanfile.cpp_info.defines=[]", out)
+        self.assertIn("[HOOK - complete_hook/complete_hook.py] post_package_info(): "
+                      "reference=%s" % REFERENCE_CACHE, out)
+        self.assertIn("[HOOK - complete_hook/complete_hook.py] post_package_info(): "
+                      "conanfile.cpp_info.defines=['ACONAN']", out)
+
     def import_hook_test(self):
         client = TestClient()
         hook_path = os.path.join(client.cache.hooks_path, "my_hook", "my_hook.py")