Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show readable error message when applying patch without (extracted) source #4738

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2607,25 +2607,29 @@ def patch_step(self, beginpath=None, patches=None):
self.log.info("Applying patch %s" % patch['name'])
trace_msg("applying patch %s" % patch['name'])

# patch source at specified index (first source if not specified)
srcind = patch.get('source', 0)
# if patch level is specified, use that (otherwise let apply_patch derive patch level)
level = patch.get('level', None)
# determine suffix of source path to apply patch in (if any)
srcpathsuffix = patch.get('sourcepath', patch.get('copy', ''))
# determine whether 'patch' file should be copied rather than applied
copy_patch = 'copy' in patch and 'sourcepath' not in patch

self.log.debug("Source index: %s; patch level: %s; source path suffix: %s; copy patch: %s",
srcind, level, srcpathsuffix, copy_patch)
self.log.debug("Patch level: %s; source path suffix: %s; copy patch: %s",
level, srcpathsuffix, copy_patch)

if beginpath is None:
try:
beginpath = self.src[srcind]['finalpath']
self.log.debug("Determine begin path for patch %s: %s" % (patch['name'], beginpath))
except IndexError as err:
raise EasyBuildError("Can't apply patch %s to source at index %s of list %s: %s",
patch['name'], srcind, self.src, err)
# If the src member is a string we have an extension with a single source.
# If that did extract the source beginpath would be set.
if isinstance(self.src, str):
raise EasyBuildError("Cannot apply patches if sources were not extracted. "
"Patch file: " + patch['name'])
if self.src:
# Use (extracted) location of first source.
# Other sources will likely not have a reasonable finalpath set.
beginpath = self.src[0]['finalpath']
self.log.debug("Determined begin path for patch %s: %s" % (patch['name'], beginpath))
else:
raise EasyBuildError("Can't apply patch %s when there are no sources!", patch['name'])
else:
self.log.debug("Using specified begin path for patch %s: %s" % (patch['name'], beginpath))

Expand Down
83 changes: 74 additions & 9 deletions test/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2020,26 +2020,30 @@ def test_exclude_path_to_top_of_module_tree(self):

def test_patch_step(self):
"""Test patch step."""
test_easyconfigs = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs', 'test_ecs')
ec = process_easyconfig(os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0.eb'))[0]
orig_sources = ec['ec']['sources'][:]
cwd = os.getcwd()

testdir = os.path.abspath(os.path.dirname(__file__))
test_easyconfigs = os.path.join(testdir, 'easyconfigs', 'test_ecs')
ec = process_easyconfig(os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0.eb'))[0]['ec']
orig_sources = ec['sources'][:]

toy_patches = [
'toy-0.0_fix-silly-typo-in-printf-statement.patch', # test for applying patch
('toy-extra.txt', 'toy-0.0'), # test for patch-by-copy
]
self.assertEqual(ec['ec']['patches'], toy_patches)
self.assertEqual(ec['patches'], toy_patches)

# test applying patches without sources
ec['ec']['sources'] = []
eb = EasyBlock(ec['ec'])
ec['sources'] = []
eb = EasyBlock(ec)
eb.fetch_step()
eb.extract_step()
self.assertErrorRegex(EasyBuildError, '.*', eb.patch_step)

# test actual patching of unpacked sources
ec['ec']['sources'] = orig_sources
eb = EasyBlock(ec['ec'])
ec['sources'] = orig_sources
change_dir(cwd)
eb = EasyBlock(ec)
eb.fetch_step()
eb.extract_step()
eb.patch_step()
Expand All @@ -2051,7 +2055,8 @@ def test_patch_step(self):

# check again with backup of patched files enabled
update_build_option('backup_patched_files', True)
eb = EasyBlock(ec['ec'])
change_dir(cwd)
eb = EasyBlock(ec)
eb.fetch_step()
eb.extract_step()
eb.patch_step()
Expand Down Expand Up @@ -2299,6 +2304,66 @@ def check_ext_start_dir(expected_start_dir, unpack_src=True, parent_startdir=Non
check_ext_start_dir(self.test_prefix, parent_startdir=self.test_prefix)
self.assertFalse(self.get_stderr())

def test_extension_patch_step(self):
"""Test start dir with extensions."""
test_easyconfigs = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs', 'test_ecs')
ec = process_easyconfig(os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0.eb'))[0]['ec']

cwd = os.getcwd()
self.assertExists(cwd)

def run_extension_step():
change_dir(cwd)
eb = EasyBlock(ec)
# Cleanup build directory
if os.path.exists(eb.builddir):
remove_dir(eb.builddir)
eb.make_builddir()
eb.update_config_template_run_step()
eb.extensions_step(fetch=True, install=True)
return os.path.join(eb.builddir)

ec['exts_defaultclass'] = 'DummyExtension'
ec['exts_list'] = [('toy', '0.0', {'easyblock': 'DummyExtension'})]

# No patches, no errors
with self.mocked_stdout_stderr():
run_extension_step()
self.assertFalse(self.get_stderr())

# Patch present, source extracted
with ec.disable_templating():
ec['exts_list'][0][2]['patches'] = [('toy-extra.txt', 'toy-0.0')]
ec['exts_list'][0][2]['unpack_source'] = True
with self.mocked_stdout_stderr():
builddir = run_extension_step()
self.assertTrue(os.path.isfile(os.path.join(builddir, 'toy', 'toy-0.0', 'toy-extra.txt')))
self.assertFalse(self.get_stderr())

# Patch but source not extracted
with ec.disable_templating():
ec['exts_list'][0][2]['unpack_source'] = False
with self.mocked_stdout_stderr():
self.assertErrorRegex(EasyBuildError, 'not extracted', run_extension_step)
self.assertFalse(self.get_stderr())

# Patch but no source
with ec.disable_templating():
ec['exts_list'][0][2]['nosource'] = True
with self.mocked_stdout_stderr():
self.assertErrorRegex(EasyBuildError, 'no sources', run_extension_step)
self.assertFalse(self.get_stderr())

# Patch without source is possible if the start_dir is set
with ec.disable_templating():
ec['start_dir'] = '%(builddir)s'
ec['exts_list'][0][2]['nosource'] = True
ec['exts_list'][0][2]['patches'] = [('toy-extra.txt', '.')]
with self.mocked_stdout_stderr():
builddir = run_extension_step()
self.assertTrue(os.path.isfile(os.path.join(builddir, 'toy-extra.txt')))
self.assertFalse(self.get_stderr())

def test_prepare_step(self):
"""Test prepare step (setting up build environment)."""
test_easyconfigs = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs', 'test_ecs')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,32 @@
@author: Kenneth Hoste (Ghent University)
"""

from easybuild.framework.easyconfig import CUSTOM
from easybuild.framework.extensioneasyblock import ExtensionEasyBlock


class DummyExtension(ExtensionEasyBlock):
"""Support for building/installing dummy extensions."""

@staticmethod
def extra_options():
"""Custom easyconfig parameters for dummy extensions."""
extra_vars = {
'unpack_source': [None, "Unpack sources", CUSTOM],
}
return ExtensionEasyBlock.extra_options(extra_vars=extra_vars)

def __init__(self, *args, **kwargs):

super(DummyExtension, self).__init__(*args, **kwargs)

# use lowercase name as default value for expected module name, and replace '-' with '_'
if 'modulename' not in self.options:
self.options['modulename'] = self.name.lower().replace('-', '_')

def run(self, unpack_src=False):
"""Install the dummy extension."""
ec_unpack_source = self.cfg.get('unpack_source')
if ec_unpack_source is not None:
unpack_src = ec_unpack_source
super(DummyExtension, self).run(unpack_src)
Loading