Repository: easybuilders/easybuild-framework

Issue:
Download instructions should tell user exactly what files to put where
The download instructions might just inform the user to "obtain software from vendor" type message, but might not tell the user exactly what filenames are expected, and where to put them. Rather than relying on each download instruction to keep the version string and whatnot up-to-date in the instructions, better to just have this output clearly written out in the stdout message. 

```
== fetching files...

Download instructions:

Blablabla current instructions foobar sources from https://download.foobar.com manually

--- 

Put "foobar-1.2.3.tar.gz" in 
/apps/sources/f/foobar/
```
or something along those lines. Maybe even print the checksum along with it?

```
You must provide the following sources:
"/apps/sources/f/foobar/foobar-1.2.3.tar.gz"  sha256sum: 3a71565a4fb114a2f9af9a1710bd3160781a7566bb2746bef1ba8af372b4d7b0
```

I know the source directory can be multiple locations, but it could at least suggest the `sourcepath/f/Foobar/` type path where I think most people end up storing them and where the download would occur if it could be downloaded.


Discussion:

Resolving patch:
diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py
--- a/easybuild/framework/easyblock.py
+++ b/easybuild/framework/easyblock.py
@@ -53,6 +53,7 @@
 import time
 import traceback
 from datetime import datetime
+from textwrap import indent
 
 import easybuild.tools.environment as env
 import easybuild.tools.toolchain as toolchain
@@ -952,7 +953,8 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
                     if download_instructions is None:
                         download_instructions = self.cfg['download_instructions']
                     if download_instructions is not None and download_instructions != "":
-                        msg = "\nDownload instructions:\n\n" + download_instructions + '\n'
+                        msg = "\nDownload instructions:\n\n" + indent(download_instructions, '    ') + '\n\n'
+                        msg += "Make the files available in the active source path: %s\n" % ':'.join(source_paths())
                         print_msg(msg, prefix=False, stderr=True)
                         error_msg += "please follow the download instructions above, and make the file available "
                         error_msg += "in the active source path (%s)" % ':'.join(source_paths())


Test patch:
diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py
--- a/test/framework/easyblock.py
+++ b/test/framework/easyblock.py
@@ -1582,7 +1582,8 @@ def test_download_instructions(self):
         self.assertErrorRegex(EasyBuildError, error_pattern, eb.fetch_step)
         stderr = self.get_stderr().strip()
         self.mock_stderr(False)
-        self.assertIn("Download instructions:\n\nManual download from example.com required", stderr)
+        self.assertIn("Download instructions:\n\n    Manual download from example.com required", stderr)
+        self.assertIn("Make the files available in the active source path", stderr)
 
         # create dummy source file
         write_file(os.path.join(os.path.dirname(self.eb_file), 'software_with_missing_sources-0.0.tar.gz'), '')
@@ -1596,7 +1597,8 @@ def test_download_instructions(self):
         stderr = self.get_stderr().strip()
         self.mock_stderr(False)
         self.mock_stdout(False)
-        self.assertIn("Download instructions:\n\nManual download from example.com required", stderr)
+        self.assertIn("Download instructions:\n\n    Manual download from example.com required", stderr)
+        self.assertIn("Make the files available in the active source path", stderr)
 
         # wipe top-level download instructions, try again
         self.contents = self.contents.replace(download_instructions, '')
@@ -1625,7 +1627,8 @@ def test_download_instructions(self):
         stderr = self.get_stderr().strip()
         self.mock_stderr(False)
         self.mock_stdout(False)
-        self.assertIn("Download instructions:\n\nExtension sources must be downloaded via example.com", stderr)
+        self.assertIn("Download instructions:\n\n    Extension sources must be downloaded via example.com", stderr)
+        self.assertIn("Make the files available in the active source path", stderr)
 
         # download instructions should also be printed if 'source_tmpl' is used to specify extension sources
         self.contents = self.contents.replace(sources, "'source_tmpl': SOURCE_TAR_GZ,")
@@ -1638,7 +1641,8 @@ def test_download_instructions(self):
         stderr = self.get_stderr().strip()
         self.mock_stderr(False)
         self.mock_stdout(False)
-        self.assertIn("Download instructions:\n\nExtension sources must be downloaded via example.com", stderr)
+        self.assertIn("Download instructions:\n\n    Extension sources must be downloaded via example.com", stderr)
+        self.assertIn("Make the files available in the active source path", stderr)
 
         # create dummy source file for extension
         write_file(os.path.join(os.path.dirname(self.eb_file), 'ext_with_missing_sources-0.0.tar.gz'), '')