Skip to content

Commit 4384a98

Browse files
committed
Normalize paths before adding/removing from $MODULEPATH
1 parent b357cd7 commit 4384a98

File tree

4 files changed

+70
-11
lines changed

4 files changed

+70
-11
lines changed

easybuild/tools/filetools.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,24 @@ def det_common_path_prefix(paths):
508508
return None
509509

510510

511+
def normalize_path(path):
512+
"""Normalize path removing empty and dot components.
513+
514+
Similar to os.path.normpath but does not resolve '..' which may return a wrong path when symlinks are used
515+
"""
516+
# In POSIX 3 or more leading slashes are equivalent to 1
517+
if path.startswith(os.path.sep):
518+
if path.startswith(os.path.sep * 2) and not path.startswith(os.path.sep * 3):
519+
start_slashes = os.path.sep * 2
520+
else:
521+
start_slashes = os.path.sep
522+
else:
523+
start_slashes = ''
524+
525+
filtered_comps = (comp for comp in path.split(os.path.sep) if comp and comp != '.')
526+
return start_slashes + os.path.sep.join(filtered_comps)
527+
528+
511529
def is_alt_pypi_url(url):
512530
"""Determine whether specified URL is already an alternate PyPI URL, i.e. whether it contains a hash."""
513531
# example: .../packages/5b/03/e135b19fadeb9b1ccb45eac9f60ca2dc3afe72d099f6bd84e03cb131f9bf/easybuild-2.7.0.tar.gz

easybuild/tools/modules.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
from easybuild.tools.config import EBROOT_ENV_VAR_ACTIONS, LOADED_MODULES_ACTIONS
4747
from easybuild.tools.config import build_option, get_modules_tool, install_path
4848
from easybuild.tools.environment import ORIG_OS_ENVIRON, restore_env, setvar, unset_env_vars
49-
from easybuild.tools.filetools import convert_name, mkdir, path_matches, read_file, which, write_file
49+
from easybuild.tools.filetools import convert_name, mkdir, normalize_path, path_matches, read_file, which, write_file
5050
from easybuild.tools.module_naming_scheme.mns import DEVEL_MODULE_SUFFIX
5151
from easybuild.tools.py2vs3 import subprocess_popen_text
5252
from easybuild.tools.run import run_cmd
@@ -381,8 +381,8 @@ def add_module_path(self, path, set_mod_paths=True):
381381
:param path: path to add to $MODULEPATH via 'use'
382382
:param set_mod_paths: (re)set self.mod_paths
383383
"""
384-
path = path.rstrip(os.path.sep)
385-
if path not in curr_module_paths():
384+
path = normalize_path(path)
385+
if path not in curr_module_paths(normalize=True):
386386
# add module path via 'module use' and make sure self.mod_paths is synced
387387
self.use(path)
388388
if set_mod_paths:
@@ -396,8 +396,8 @@ def remove_module_path(self, path, set_mod_paths=True):
396396
:param set_mod_paths: (re)set self.mod_paths
397397
"""
398398
# remove module path via 'module unuse' and make sure self.mod_paths is synced
399-
path = path.rstrip(os.path.sep)
400-
if path in curr_module_paths():
399+
path = normalize_path(path)
400+
if path in curr_module_paths(normalize=True):
401401
self.unuse(path)
402402

403403
if set_mod_paths:
@@ -1294,8 +1294,8 @@ def remove_module_path(self, path, set_mod_paths=True):
12941294
# remove module path via 'module use' and make sure self.mod_paths is synced
12951295
# modulecmd.tcl keeps track of how often a path was added via 'module use',
12961296
# so we need to check to make sure it's really removed
1297-
path = path.rstrip(os.path.sep)
1298-
while path in curr_module_paths():
1297+
path = normalize_path(path)
1298+
while path in curr_module_paths(normalize=True):
12991299
self.unuse(path)
13001300
if set_mod_paths:
13011301
self.set_mod_paths()
@@ -1446,7 +1446,7 @@ def use(self, path, priority=None):
14461446
if os.environ.get('__LMOD_Priority_MODULEPATH'):
14471447
self.run_module(['use', path])
14481448
else:
1449-
path = path.rstrip(os.path.sep)
1449+
path = normalize_path(path)
14501450
cur_mod_path = os.environ.get('MODULEPATH')
14511451
if cur_mod_path is None:
14521452
new_mod_path = path
@@ -1467,7 +1467,7 @@ def unuse(self, path):
14671467
self.log.debug('Changing MODULEPATH from %s to <unset>' % cur_mod_path)
14681468
del os.environ['MODULEPATH']
14691469
else:
1470-
path = path.rstrip(os.path.sep)
1470+
path = normalize_path(path)
14711471
new_mod_path = ':'.join(p for p in cur_mod_path.split(':') if p != path)
14721472
if new_mod_path != cur_mod_path:
14731473
self.log.debug('Changing MODULEPATH from %s to %s' % (cur_mod_path, new_mod_path))
@@ -1618,12 +1618,17 @@ def get_software_version(name):
16181618
return version
16191619

16201620

1621-
def curr_module_paths():
1621+
def curr_module_paths(normalize=False):
16221622
"""
16231623
Return a list of current module paths.
1624+
1625+
:param normalize: Normalize the paths
16241626
"""
16251627
# avoid empty or nonexistent paths, which don't make any sense
1626-
return [p for p in os.environ.get('MODULEPATH', '').split(':') if p and os.path.exists(p)]
1628+
module_paths = (p for p in os.environ.get('MODULEPATH', '').split(':') if p and os.path.exists(p))
1629+
if normalize:
1630+
module_paths = (normalize_path(p) for p in module_paths)
1631+
return list(module_paths)
16271632

16281633

16291634
def mk_module_path(paths):

test/framework/filetools.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,21 @@ def test_common_path_prefix(self):
351351
self.assertEqual(ft.det_common_path_prefix(['foo']), None)
352352
self.assertEqual(ft.det_common_path_prefix([]), None)
353353

354+
def test_normalize_path(self):
355+
"""Test normalize_path"""
356+
self.assertEqual(ft.normalize_path(''), '')
357+
self.assertEqual(ft.normalize_path('/'), '/')
358+
self.assertEqual(ft.normalize_path('//'), '//')
359+
self.assertEqual(ft.normalize_path('///'), '/')
360+
self.assertEqual(ft.normalize_path('/foo/bar/baz'), '/foo/bar/baz')
361+
self.assertEqual(ft.normalize_path('/foo//bar/././baz/'), '/foo/bar/baz')
362+
self.assertEqual(ft.normalize_path('foo//bar/././baz/'), 'foo/bar/baz')
363+
self.assertEqual(ft.normalize_path('//foo//bar/././baz/'), '//foo/bar/baz')
364+
self.assertEqual(ft.normalize_path('///foo//bar/././baz/'), '/foo/bar/baz')
365+
self.assertEqual(ft.normalize_path('////foo//bar/././baz/'), '/foo/bar/baz')
366+
self.assertEqual(ft.normalize_path('/././foo//bar/././baz/'), '/foo/bar/baz')
367+
self.assertEqual(ft.normalize_path('//././foo//bar/././baz/'), '//foo/bar/baz')
368+
354369
def test_download_file(self):
355370
"""Test download_file function."""
356371
fn = 'toy-0.0.tar.gz'

test/framework/modules.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,27 @@ def test_add_and_remove_module_path(self):
12591259
self.modtool.remove_module_path(os.path.join(test_dir1, ''))
12601260
self.assertEqual(os.environ.get('MODULEPATH', ''), '')
12611261

1262+
# And with some more trickery
1263+
test_dir1_relative = os.path.join(test_dir1, '..', os.path.basename(test_dir1))
1264+
test_dir2_dot = os.path.join(os.path.dirname(test_dir2), '.', os.path.basename(test_dir2))
1265+
self.modtool.add_module_path(test_dir1_relative)
1266+
self.assertEqual(os.environ['MODULEPATH'], test_dir1_relative)
1267+
self.modtool.add_module_path(test_dir1)
1268+
self.assertEqual(os.environ['MODULEPATH'], test_dir1 + ':' + test_dir1_relative)
1269+
self.modtool.remove_module_path(test_dir1)
1270+
self.assertEqual(os.environ['MODULEPATH'], test_dir1_relative)
1271+
self.modtool.add_module_path(test_dir2_dot)
1272+
self.assertEqual(os.environ['MODULEPATH'], test_dir2 + ':' + test_dir1_relative)
1273+
self.modtool.remove_module_path(test_dir2_dot)
1274+
self.assertEqual(os.environ['MODULEPATH'], test_dir1_relative)
1275+
# Force adding such a dot path which can be removed with either variant
1276+
os.environ['MODULEPATH'] = test_dir2_dot + ':' + test_dir1_relative
1277+
self.modtool.remove_module_path(test_dir2_dot)
1278+
self.assertEqual(os.environ['MODULEPATH'], test_dir1_relative)
1279+
os.environ['MODULEPATH'] = test_dir2_dot + ':' + test_dir1_relative
1280+
self.modtool.remove_module_path(test_dir2)
1281+
self.assertEqual(os.environ['MODULEPATH'], test_dir1_relative)
1282+
12621283
os.environ['MODULEPATH'] = old_module_path # Restore
12631284

12641285
def test_module_use_bash(self):

0 commit comments

Comments
 (0)