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

ensure that path configuration options have absolute path values #3832

Merged
merged 7 commits into from
Sep 15, 2021
44 changes: 43 additions & 1 deletion easybuild/tools/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -1047,8 +1047,50 @@ def _postprocess_checks(self):

self.log.info("Checks on configuration options passed")

def get_cfg_opt_abs_path(self, opt_name, path):
"""Get path value of configuration option as absolute path."""
if os.path.isabs(path):
abs_path = path
else:
abs_path = os.path.abspath(path)
self.log.info("Relative path value for '%s' configuration option resolved to absolute path: %s",
opt_name, abs_path)
return abs_path

def _ensure_abs_path(self, opt_name):
"""Ensure that path value for specified configuration option is an absolute path."""

opt_val = getattr(self.options, opt_name)
if opt_val:
if isinstance(opt_val, string_type):
setattr(self.options, opt_name, self.get_cfg_opt_abs_path(opt_name, opt_val))
elif isinstance(opt_val, list):
abs_paths = [self.get_cfg_opt_abs_path(opt_name, p) for p in opt_val]
akesandgren marked this conversation as resolved.
Show resolved Hide resolved
setattr(self.options, opt_name, abs_paths)
else:
error_msg = "Don't know how to ensure absolute path(s) for '%s' configuration option (value type: %s)"
raise EasyBuildError(error_msg, opt_name, type(opt_val))

def _postprocess_config(self):
"""Postprocessing of configuration options"""

# resolve relative paths for configuration options that specify a location
path_opt_names = ['buildpath', 'containerpath', 'git_working_dirs_path', 'installpath',
'installpath_modules', 'installpath_software', 'prefix', 'packagepath',
'robot', 'robot_paths', 'sourcepath']
boegel marked this conversation as resolved.
Show resolved Hide resolved

# repositorypath is a special case: only first part is a path;
# 2nd (optional) part is a relative subdir and should not be resolved to an absolute path!
repositorypath = self.options.repositorypath
if isinstance(repositorypath, (list, tuple)) and len(repositorypath) == 2:
abs_path = self.get_cfg_opt_abs_path('repositorypath', repositorypath[0])
self.options.repositorypath = (abs_path, repositorypath[1])
else:
path_opt_names.append('repositorypath')

for opt_name in path_opt_names:
self._ensure_abs_path(opt_name)

if self.options.prefix is not None:
# prefix applies to all paths, and repository has to be reinitialised to take new repositorypath in account
# in the legacy-style configuration, repository is initialised in configuration file itself
Expand Down Expand Up @@ -1091,7 +1133,7 @@ def _postprocess_config(self):

# paths specified to --robot have preference over --robot-paths
# keep both values in sync if robot is enabled, which implies enabling dependency resolver
self.options.robot_paths = [os.path.abspath(path) for path in self.options.robot + self.options.robot_paths]
self.options.robot_paths = self.options.robot + self.options.robot_paths
self.options.robot = self.options.robot_paths

# Update the search_paths (if any) to absolute paths
Expand Down
2 changes: 1 addition & 1 deletion test/framework/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ def test_generaloption_config_file(self):
self.assertEqual(install_path('mod'), installpath_modules), # via config file
self.assertEqual(source_paths(), [testpath2]) # via command line
self.assertEqual(build_path(), testpath1) # via config file
self.assertEqual(get_repositorypath(), [os.path.join(topdir, 'ebfiles_repo'), 'somesubdir']) # via config file
self.assertEqual(get_repositorypath(), (os.path.join(topdir, 'ebfiles_repo'), 'somesubdir')) # via config file

# hardcoded first entry
self.assertEqual(options.robot_paths[0], '/tmp/foo')
Expand Down
62 changes: 62 additions & 0 deletions test/framework/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -6233,6 +6233,68 @@ def test_accept_eula_for(self):
self.eb_main(args, do_build=True, raise_error=True)
self.assertTrue(os.path.exists(toy_modfile))

def test_config_abs_path(self):
"""Test ensuring of absolute path values for path configuration options."""

test_topdir = os.path.join(self.test_prefix, 'test_topdir')
test_subdir = os.path.join(test_topdir, 'test_middle_dir', 'test_subdir')
mkdir(test_subdir, parents=True)
change_dir(test_subdir)

# a relative path specified in a configuration file is positively weird, but fine :)
cfgfile = os.path.join(self.test_prefix, 'test.cfg')
cfgtxt = '\n'.join([
"[config]",
"containerpath = ..",
"repositorypath = /apps/easyconfigs_archive, somesubdir",
])
write_file(cfgfile, cfgtxt)

# relative paths in environment variables is also weird,
# but OK for the sake of testing...
os.environ['EASYBUILD_INSTALLPATH'] = '../..'
os.environ['EASYBUILD_ROBOT_PATHS'] = '../..'

args = [
'--configfiles=%s' % cfgfile,
'--prefix=..',
'--sourcepath=.',
'--show-config',
]

txt, _ = self._run_mock_eb(args, do_build=True, raise_error=True, testing=False, strip=True)

patterns = [
r"^containerpath\s+\(F\) = /.*/test_topdir/test_middle_dir$",
r"^installpath\s+\(E\) = /.*/test_topdir$",
r"^prefix\s+\(C\) = /.*/test_topdir/test_middle_dir$",
r"^repositorypath\s+\(F\) = \('/apps/easyconfigs_archive', ' somesubdir'\)$",
r"^sourcepath\s+\(C\) = /.*/test_topdir/test_middle_dir/test_subdir$",
r"^robot-paths\s+\(E\) = /.*/test_topdir$",
]
for pattern in patterns:
regex = re.compile(pattern, re.M)
self.assertTrue(regex.search(txt), "Pattern '%s' should be found in: %s" % (pattern, txt))

# if --robot is also used, that wins and $EASYBUILD_ROBOT_PATHS doesn't matter anymore
akesandgren marked this conversation as resolved.
Show resolved Hide resolved
change_dir(test_subdir)
args.append('--robot=..:.')
txt, _ = self._run_mock_eb(args, do_build=True, raise_error=True, testing=False, strip=True)

patterns.pop(-1)
robot_value_pattern = ', '.join([
r'/.*/test_topdir/test_middle_dir', # via --robot (first path)
r'/.*/test_topdir/test_middle_dir/test_subdir', # via --robot (second path)
r'/.*/test_topdir', # via $EASYBUILD_ROBOT_PATHS
])
patterns.extend([
r"^robot-paths\s+\(C\) = %s$" % robot_value_pattern,
r"^robot\s+\(C\) = %s$" % robot_value_pattern,
])
for pattern in patterns:
regex = re.compile(pattern, re.M)
self.assertTrue(regex.search(txt), "Pattern '%s' should be found in: %s" % (pattern, txt))

# end-to-end testing of unknown filename
def test_easystack_wrong_read(self):
"""Test for --easystack <easystack.yaml> when wrong name is provided"""
Expand Down