Repository: easybuilders/easybuild-framework

Issue:
allow setting post message from easyconfig file
In some cases, it would be interesting to allow setting the post message that is printed at the end of the installation process in the easyconfig file, e.g. for issuing warnings in case like `bbcp` which requires certain ports to be open for it to work properly.

The `easyblock` module already supports this via the `self.postmsg` class variable.



Discussion:
+1 :) Have the case for LikWid where a setuid procedure is required for the daemon. 
@Falkor What's the use case you have in mind?
I just updated the easybuild recipes for [Likwid](https://github.com/RRZE-HPC/likwid) that relies on the `likwid-accessD` daemon (built under `$EBROOTLIKWID/sbin`) to forwards the read and write requests to the MSR registers with higher priviledges as per [power] checks -- you will soon have the merge requests. 

There are 3 ways to do that (setuid / setgid or libcap methods), only the first one actually works, but in all cases it would be worth to give the [sudo] commands to perform to the user.
So far, and unless I'm wrong, I can only do that by adding some messages as part of the `postinstallcmds`, typically as follows: 

```python
postinstallcmds = [
    'echo "IMPORTANT: you will have to manually apply the following changes:"',
    'echo "  sudo chown root:root \$EBROOTLIKWID/sbin/likwid-accessD && sudo chmod u+s \$EBROOTLIKWID/sbin/likwid-accessD"'
]
```
but that hides it in the build logs as they are not exposed to the user on the regular `eb` process. 
In this case, having the possibility to provide post messages would be grateful. 

@victorusu was looking for suggestions for contributions, maybe this is an easy one to tackle...

Resolving patch:
diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py
--- a/easybuild/framework/easyblock.py
+++ b/easybuild/framework/easyblock.py
@@ -2952,6 +2952,14 @@ def apply_post_install_patches(self, patches=None):
             # To allow postinstallpatches for Bundle, and derived, easyblocks we directly call EasyBlock.patch_step
             EasyBlock.patch_step(self, beginpath=self.installdir, patches=patches)
 
+    def print_post_install_messages(self):
+        """
+        Print post-install messages that are specified via the 'postinstallmsgs' easyconfig parameter.
+        """
+        msgs = self.cfg['postinstallmsgs'] or []
+        for msg in msgs:
+            print_msg(msg, log=self.log)
+
     def post_install_step(self):
         """
         Do some postprocessing
@@ -2960,6 +2968,7 @@ def post_install_step(self):
 
         self.run_post_install_commands()
         self.apply_post_install_patches()
+        self.print_post_install_messages()
 
         self.fix_shebang()
 
diff --git a/easybuild/framework/easyconfig/default.py b/easybuild/framework/easyconfig/default.py
--- a/easybuild/framework/easyconfig/default.py
+++ b/easybuild/framework/easyconfig/default.py
@@ -116,6 +116,7 @@
     'pretestopts': ['', 'Extra prefix options for test.', BUILD],
     'postinstallcmds': [[], 'Commands to run after the install step.', BUILD],
     'postinstallpatches': [[], 'Patch files to apply after running the install step.', BUILD],
+    'postinstallmsgs': [[], 'Messages to print after running the install step.', BUILD],
     'required_linked_shared_libs': [[], "List of shared libraries (names, file names, or paths) which must be "
                                         "linked in all installed binaries/libraries", BUILD],
     'runtest': [None, ('Indicates if a test should be run after make; should specify argument '


Test patch:
diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py
--- a/test/framework/toy_build.py
+++ b/test/framework/toy_build.py
@@ -3778,6 +3778,35 @@ def test_toy_unavailable_os_dep(self):
             regex = re.compile(pattern, re.M)
             self.assertTrue(regex.search(stdout), "Pattern '%s' should be found in: %s" % (regex.pattern, stdout))
 
+    def test_toy_post_install_messages(self):
+        """
+        Test use of post-install messages
+        """
+        test_ecs = os.path.join(os.path.dirname(__file__), 'easyconfigs', 'test_ecs')
+        toy_ec = os.path.join(test_ecs, 't', 'toy', 'toy-0.0.eb')
+
+        test_ec_txt = read_file(toy_ec)
+        test_ec_txt += "\npostinstallmsgs = ['This is post install message 1', 'This is post install message 2']"
+        test_ec = os.path.join(self.test_prefix, 'test.eb')
+        write_file(test_ec, test_ec_txt)
+
+        test_report_fp = os.path.join(self.test_buildpath, 'full_test_report.md')
+
+        self.mock_stderr(True)
+        self.mock_stdout(True)
+        self.test_toy_build(ec_file=test_ec, test_report=test_report_fp)
+        stdout = self.get_stdout()
+        self.mock_stderr(False)
+        self.mock_stdout(False)
+
+        patterns = [
+            r"== This is post install message 1",
+            r"== This is post install message 2",
+        ]
+        for pattern in patterns:
+            regex = re.compile(pattern, re.M)
+            self.assertTrue(regex.search(stdout), "Pattern '%s' should be found in: %s" % (regex.pattern, stdout))
+
 
 def suite():
     """ return all the tests in this file """