Repository: easybuilders/easybuild-framework

Issue:
inject-checksum creates "too long line"
On a python extension with multiple patches over multiple lines (due to being too long for a single line), inject-checksum merges those patches into a single line, causing travis to complain with "too long line"

See for instance, TensorFlow-1.12.0-fosscuda-2018b-Python-2.7.15.eb (easyconfig PR #7246)


Discussion:
fixed in #3025

Resolving patch:
diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py
--- a/easybuild/framework/easyblock.py
+++ b/easybuild/framework/easyblock.py
@@ -3332,7 +3332,13 @@ def inject_checksums(ecs, checksum_type):
     :param ecs: list of EasyConfig instances to inject checksums into corresponding files
     :param checksum_type: type of checksum to use
     """
+    def make_list_lines(values, indent_level):
+        """Make lines for list of values."""
+        line_indent = INDENT_4SPACES * indent_level
+        return [line_indent + "'%s'," % x for x in values]
+
     def make_checksum_lines(checksums, indent_level):
+        """Make lines for list of checksums."""
         line_indent = INDENT_4SPACES * indent_level
         checksum_lines = []
         for fn, checksum in checksums:
@@ -3466,8 +3472,15 @@ def make_checksum_lines(checksums, indent_level):
 
                     for key, val in sorted(ext_options.items()):
                         if key != 'checksums' and val != exts_default_options.get(key):
-                            val = quote_str(val, prefer_single_quotes=True)
-                            exts_list_lines.append("%s'%s': %s," % (INDENT_4SPACES * 2, key, val))
+                            strval = quote_str(val, prefer_single_quotes=True)
+                            line = "%s'%s': %s," % (INDENT_4SPACES * 2, key, strval)
+                            # fix long lines for list-type values (e.g. patches)
+                            if len(val) > 1 and isinstance(val, list):
+                                exts_list_lines.append("%s'%s': [" % (INDENT_4SPACES * 2, key))
+                                exts_list_lines.extend(make_list_lines(val, indent_level=3))
+                                exts_list_lines.append(INDENT_4SPACES * 2 + '],',)
+                            else:
+                                exts_list_lines.append(line)
 
                     # if any checksums were collected, inject them for this extension
                     if ext_checksums:


Test patch:
diff --git a/test/framework/easyconfigs/test_ecs/t/toy/toy-0.0-gompi-2018a-test.eb b/test/framework/easyconfigs/test_ecs/t/toy/toy-0.0-gompi-2018a-test.eb
--- a/test/framework/easyconfigs/test_ecs/t/toy/toy-0.0-gompi-2018a-test.eb
+++ b/test/framework/easyconfigs/test_ecs/t/toy/toy-0.0-gompi-2018a-test.eb
@@ -33,7 +33,10 @@ exts_list = [
         'checksums': ['f3676716b610545a4e8035087f5be0a0248adee0abb3930d3edb76d498ae91e7'],  # checksum for 
         # custom extension filter to verify use of stdin value being passed to filter command
     'exts_filter': ("cat | grep '^bar$'", '%(name)s'),
-        'patches': ['bar-0.0_fix-silly-typo-in-printf-statement.patch'],
+        'patches': [
+            'bar-0.0_fix-silly-typo-in-printf-statement.patch',
+            'bar-0.0_fix-very-silly-typo-in-printf-statement.patch',
+        ],
         'toy_ext_param': "mv anotherbar bar_bis",
         'unknowneasyconfigparameterthatshouldbeignored': 'foo',
     }),
diff --git a/test/framework/options.py b/test/framework/options.py
--- a/test/framework/options.py
+++ b/test/framework/options.py
@@ -3899,6 +3899,8 @@ def test_inject_checksums(self):
         bar_tar_gz_sha256 = 'f3676716b610545a4e8035087f5be0a0248adee0abb3930d3edb76d498ae91e7'
         bar_patch = 'bar-0.0_fix-silly-typo-in-printf-statement.patch'
         bar_patch_sha256 = '84db53592e882b5af077976257f9c7537ed971cb2059003fd4faa05d02cae0ab'
+        bar_patch_bis = 'bar-0.0_fix-very-silly-typo-in-printf-statement.patch'
+        bar_patch_bis_sha256 = 'd0bf102f9c5878445178c5f49b7cd7546e704c33fe2060c7354b7e473cfeb52b'
         patterns = [
             "^== injecting sha256 checksums in .*/test\.eb$",
             "^== fetching sources & patches for test\.eb\.\.\.$",
@@ -3909,6 +3911,7 @@ def test_inject_checksums(self):
             "^== injecting sha256 checksums for extensions in test\.eb\.\.\.$",
             "^==  \* bar-0\.0\.tar\.gz: %s$" % bar_tar_gz_sha256,
             "^==  \* %s: %s$" % (bar_patch, bar_patch_sha256),
+            "^==  \* %s: %s$" % (bar_patch_bis, bar_patch_bis_sha256),
             "^==  \* barbar-0\.0\.tar\.gz: a33100d1837d6d54edff7d19f195056c4bd9a4c8d399e72feaf90f0216c4c91c$",
         ]
         for pattern in patterns:
@@ -3928,9 +3931,23 @@ def test_inject_checksums(self):
         regex = re.compile("^[ ]*'%s',  # bar-0.0.tar.gz$" % bar_tar_gz_sha256, re.M)
         self.assertTrue(regex.search(ec_txt), "Pattern '%s' found in: %s" % (regex.pattern, ec_txt))
 
-        # no single-line checksum entry for bar*.patch, since line would be > 120 chars
-        regex = re.compile("^[ ]*# %s\n[ ]*'%s',$" % (bar_patch, bar_patch_sha256), re.M)
-        self.assertTrue(regex.search(ec_txt), "Pattern '%s' found in: %s" % (regex.pattern, ec_txt))
+        # no single-line checksum entry for bar patches, since line would be > 120 chars
+        bar_patch_patterns = [
+            r"^[ ]*# %s\n[ ]*'%s',$" % (bar_patch, bar_patch_sha256),
+            r"^[ ]*# %s\n[ ]*'%s',$" % (bar_patch_bis, bar_patch_bis_sha256),
+        ]
+        for pattern in bar_patch_patterns:
+            regex = re.compile(pattern, re.M)
+            self.assertTrue(regex.search(ec_txt), "Pattern '%s' found in: %s" % (regex.pattern, ec_txt))
+
+        # no single-line entry for bar patches themselves, since line would be too long
+        bar_patch_patterns = [
+            r"^[ ]*'%s',$" % bar_patch,
+            r"^[ ]*'%s',$" % bar_patch_bis,
+        ]
+        for pattern in bar_patch_patterns:
+            regex = re.compile(pattern, re.M)
+            self.assertTrue(regex.search(ec_txt), "Pattern '%s' found in: %s" % (regex.pattern, ec_txt))
 
         # name/version of toy should NOT be hardcoded in exts_list, 'name'/'version' parameters should be used
         self.assertTrue('    (name, version, {' in ec_txt)
@@ -3956,9 +3973,10 @@ def test_inject_checksums(self):
             'checksums': [
                 bar_tar_gz_sha256,
                 bar_patch_sha256,
+                bar_patch_bis_sha256,
             ],
             'exts_filter': ("cat | grep '^bar$'", '%(name)s'),
-            'patches': [bar_patch],
+            'patches': [bar_patch, bar_patch_bis],
             'toy_ext_param': "mv anotherbar bar_bis",
             'unknowneasyconfigparameterthatshouldbeignored': 'foo',
         }))
diff --git a/test/framework/sandbox/sources/toy/extensions/bar-0.0_fix-very-silly-typo-in-printf-statement.patch b/test/framework/sandbox/sources/toy/extensions/bar-0.0_fix-very-silly-typo-in-printf-statement.patch
new file mode 100644
--- /dev/null
+++ b/test/framework/sandbox/sources/toy/extensions/bar-0.0_fix-very-silly-typo-in-printf-statement.patch
@@ -0,0 +1,10 @@
+--- bar.source.orig	2019-09-21 15:06:10.000000000 +0200
++++ bar.source	2019-09-21 15:06:20.000000000 +0200
+@@ -2,6 +2,6 @@
+ 
+ int main(int argc, char* argv[]){
+ 
+-    printf("I'm a bar, and very proud of it.\n");
++    printf("I'm a bar, and very very proud of it.\n");
+     return 0;
+ }