Prefer llnl.util.symlink.readlink to os.readlink (#44126)
Symlinks on Windows can use longpath prefixes (\\?\); these are fine in the context of win32 API interactions but break numerous facets of Spack behavior that rely on string parsing/matching (archiving, binary distributions, tarball extraction, view regen, etc). Spack's internal readlink method (llnl.util.symlink.readlink) gracefully handles this by removing the prefix and otherwise behaving exactly as os.readlink does, so we should prefer that in all cases.
This commit is contained in:
parent
1b14170bd1
commit
04258f9cce
16 changed files with 42 additions and 28 deletions
|
@ -822,7 +822,7 @@ def copy_tree(
|
||||||
if islink(s):
|
if islink(s):
|
||||||
link_target = resolve_link_target_relative_to_the_link(s)
|
link_target = resolve_link_target_relative_to_the_link(s)
|
||||||
if symlinks:
|
if symlinks:
|
||||||
target = os.readlink(s)
|
target = readlink(s)
|
||||||
if os.path.isabs(target):
|
if os.path.isabs(target):
|
||||||
|
|
||||||
def escaped_path(path):
|
def escaped_path(path):
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
import llnl.util.filesystem as fs
|
import llnl.util.filesystem as fs
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
import spack.config
|
import spack.config
|
||||||
import spack.hash_types as ht
|
import spack.hash_types as ht
|
||||||
|
@ -181,7 +182,7 @@ def deprecated_file_path(self, deprecated_spec, deprecator_spec=None):
|
||||||
base_dir = (
|
base_dir = (
|
||||||
self.path_for_spec(deprecator_spec)
|
self.path_for_spec(deprecator_spec)
|
||||||
if deprecator_spec
|
if deprecator_spec
|
||||||
else os.readlink(deprecated_spec.prefix)
|
else readlink(deprecated_spec.prefix)
|
||||||
)
|
)
|
||||||
|
|
||||||
yaml_path = os.path.join(
|
yaml_path = os.path.join(
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
import llnl.util.tty.color as clr
|
import llnl.util.tty.color as clr
|
||||||
from llnl.util.link_tree import ConflictingSpecsError
|
from llnl.util.link_tree import ConflictingSpecsError
|
||||||
from llnl.util.symlink import symlink
|
from llnl.util.symlink import readlink, symlink
|
||||||
|
|
||||||
import spack.compilers
|
import spack.compilers
|
||||||
import spack.concretize
|
import spack.concretize
|
||||||
|
@ -662,7 +662,7 @@ def _current_root(self):
|
||||||
if not os.path.islink(self.root):
|
if not os.path.islink(self.root):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
root = os.readlink(self.root)
|
root = readlink(self.root)
|
||||||
if os.path.isabs(root):
|
if os.path.isabs(root):
|
||||||
return root
|
return root
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
import archspec.cpu
|
import archspec.cpu
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
import spack.target
|
import spack.target
|
||||||
import spack.version
|
import spack.version
|
||||||
|
@ -133,7 +134,7 @@ def craype_type_and_version(cls):
|
||||||
# Take the default version from known symlink path
|
# Take the default version from known symlink path
|
||||||
default_path = os.path.join(craype_dir, "default")
|
default_path = os.path.join(craype_dir, "default")
|
||||||
if os.path.islink(default_path):
|
if os.path.islink(default_path):
|
||||||
version = spack.version.Version(os.readlink(default_path))
|
version = spack.version.Version(readlink(default_path))
|
||||||
return (craype_type, version)
|
return (craype_type, version)
|
||||||
|
|
||||||
# If no default version, sort available versions and return latest
|
# If no default version, sort available versions and return latest
|
||||||
|
|
|
@ -565,7 +565,7 @@ def make_link_relative(new_links, orig_links):
|
||||||
orig_links (list): original links
|
orig_links (list): original links
|
||||||
"""
|
"""
|
||||||
for new_link, orig_link in zip(new_links, orig_links):
|
for new_link, orig_link in zip(new_links, orig_links):
|
||||||
target = os.readlink(orig_link)
|
target = readlink(orig_link)
|
||||||
relative_target = os.path.relpath(target, os.path.dirname(orig_link))
|
relative_target = os.path.relpath(target, os.path.dirname(orig_link))
|
||||||
os.unlink(new_link)
|
os.unlink(new_link)
|
||||||
symlink(relative_target, new_link)
|
symlink(relative_target, new_link)
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from llnl.util.symlink import symlink
|
from llnl.util.symlink import readlink, symlink
|
||||||
|
|
||||||
import spack.binary_distribution as bindist
|
import spack.binary_distribution as bindist
|
||||||
import spack.error
|
import spack.error
|
||||||
|
@ -26,7 +26,7 @@ def _relocate_spliced_links(links, orig_prefix, new_prefix):
|
||||||
in our case. This still needs to be called after the copy to destination
|
in our case. This still needs to be called after the copy to destination
|
||||||
because it expects the new directory structure to be in place."""
|
because it expects the new directory structure to be in place."""
|
||||||
for link in links:
|
for link in links:
|
||||||
link_target = os.readlink(os.path.join(orig_prefix, link))
|
link_target = readlink(os.path.join(orig_prefix, link))
|
||||||
link_target = re.sub("^" + orig_prefix, new_prefix, link_target)
|
link_target = re.sub("^" + orig_prefix, new_prefix, link_target)
|
||||||
new_link_path = os.path.join(new_prefix, link)
|
new_link_path = os.path.join(new_prefix, link)
|
||||||
os.unlink(new_link_path)
|
os.unlink(new_link_path)
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
import archspec.cpu
|
import archspec.cpu
|
||||||
|
|
||||||
from llnl.util.filesystem import join_path, visit_directory_tree
|
from llnl.util.filesystem import join_path, visit_directory_tree
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
import spack.binary_distribution as bindist
|
import spack.binary_distribution as bindist
|
||||||
import spack.caches
|
import spack.caches
|
||||||
|
@ -1062,10 +1063,10 @@ def test_tarball_common_prefix(dummy_prefix, tmpdir):
|
||||||
assert set(os.listdir(os.path.join("prefix2", "share"))) == {"file"}
|
assert set(os.listdir(os.path.join("prefix2", "share"))) == {"file"}
|
||||||
|
|
||||||
# Relative symlink should still be correct
|
# Relative symlink should still be correct
|
||||||
assert os.readlink(os.path.join("prefix2", "bin", "relative_app_link")) == "app"
|
assert readlink(os.path.join("prefix2", "bin", "relative_app_link")) == "app"
|
||||||
|
|
||||||
# Absolute symlink should remain absolute -- this is for relocation to fix up.
|
# Absolute symlink should remain absolute -- this is for relocation to fix up.
|
||||||
assert os.readlink(os.path.join("prefix2", "bin", "absolute_app_link")) == os.path.join(
|
assert readlink(os.path.join("prefix2", "bin", "absolute_app_link")) == os.path.join(
|
||||||
dummy_prefix, "bin", "app"
|
dummy_prefix, "bin", "app"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import llnl.util.filesystem as fs
|
import llnl.util.filesystem as fs
|
||||||
import llnl.util.link_tree
|
import llnl.util.link_tree
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
import spack.cmd.env
|
import spack.cmd.env
|
||||||
import spack.config
|
import spack.config
|
||||||
|
@ -4414,8 +4415,8 @@ def test_env_view_resolves_identical_file_conflicts(tmp_path, install_mockery, m
|
||||||
# view-file/bin/
|
# view-file/bin/
|
||||||
# x # expect this x to be linked
|
# x # expect this x to be linked
|
||||||
|
|
||||||
assert os.readlink(tmp_path / "view" / "bin" / "x") == bottom.bin.x
|
assert readlink(tmp_path / "view" / "bin" / "x") == bottom.bin.x
|
||||||
assert os.readlink(tmp_path / "view" / "bin" / "y") == top.bin.y
|
assert readlink(tmp_path / "view" / "bin" / "y") == top.bin.y
|
||||||
|
|
||||||
|
|
||||||
def test_env_view_ignores_different_file_conflicts(tmp_path, install_mockery, mock_fetch):
|
def test_env_view_ignores_different_file_conflicts(tmp_path, install_mockery, mock_fetch):
|
||||||
|
@ -4426,4 +4427,4 @@ def test_env_view_ignores_different_file_conflicts(tmp_path, install_mockery, mo
|
||||||
install()
|
install()
|
||||||
prefix_dependent = e.matching_spec("view-ignore-conflict").prefix
|
prefix_dependent = e.matching_spec("view-ignore-conflict").prefix
|
||||||
# The dependent's file is linked into the view
|
# The dependent's file is linked into the view
|
||||||
assert os.readlink(tmp_path / "view" / "bin" / "x") == prefix_dependent.bin.x
|
assert readlink(tmp_path / "view" / "bin" / "x") == prefix_dependent.bin.x
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import llnl.util.filesystem as fs
|
import llnl.util.filesystem as fs
|
||||||
from llnl.util.symlink import islink, symlink
|
from llnl.util.symlink import islink, readlink, symlink
|
||||||
|
|
||||||
import spack.paths
|
import spack.paths
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ def test_symlinks_true(self, stage):
|
||||||
|
|
||||||
assert os.path.exists("dest/a/b2")
|
assert os.path.exists("dest/a/b2")
|
||||||
with fs.working_dir("dest/a"):
|
with fs.working_dir("dest/a"):
|
||||||
assert os.path.exists(os.readlink("b2"))
|
assert os.path.exists(readlink("b2"))
|
||||||
|
|
||||||
assert os.path.realpath("dest/f/2") == os.path.abspath("dest/a/b/2")
|
assert os.path.realpath("dest/f/2") == os.path.abspath("dest/a/b/2")
|
||||||
assert os.path.realpath("dest/2") == os.path.abspath("dest/1")
|
assert os.path.realpath("dest/2") == os.path.abspath("dest/1")
|
||||||
|
@ -281,7 +281,7 @@ def test_allow_broken_symlinks(self, stage):
|
||||||
symlink("nonexistant.txt", "source/broken", allow_broken_symlinks=True)
|
symlink("nonexistant.txt", "source/broken", allow_broken_symlinks=True)
|
||||||
fs.install_tree("source", "dest", symlinks=True, allow_broken_symlinks=True)
|
fs.install_tree("source", "dest", symlinks=True, allow_broken_symlinks=True)
|
||||||
assert os.path.islink("dest/broken")
|
assert os.path.islink("dest/broken")
|
||||||
assert not os.path.exists(os.readlink("dest/broken"))
|
assert not os.path.exists(readlink("dest/broken"))
|
||||||
|
|
||||||
def test_glob_src(self, stage):
|
def test_glob_src(self, stage):
|
||||||
"""Test using a glob as the source."""
|
"""Test using a glob as the source."""
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
import spack.cmd.modules
|
import spack.cmd.modules
|
||||||
import spack.config
|
import spack.config
|
||||||
import spack.error
|
import spack.error
|
||||||
|
@ -78,7 +80,7 @@ def test_modules_default_symlink(
|
||||||
|
|
||||||
link_path = os.path.join(os.path.dirname(mock_module_filename), "default")
|
link_path = os.path.join(os.path.dirname(mock_module_filename), "default")
|
||||||
assert os.path.islink(link_path)
|
assert os.path.islink(link_path)
|
||||||
assert os.readlink(link_path) == mock_module_filename
|
assert readlink(link_path) == mock_module_filename
|
||||||
|
|
||||||
generator.remove()
|
generator.remove()
|
||||||
assert not os.path.lexists(link_path)
|
assert not os.path.lexists(link_path)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from llnl.util import filesystem as fs
|
from llnl.util import filesystem as fs
|
||||||
from llnl.util.symlink import symlink
|
from llnl.util.symlink import readlink, symlink
|
||||||
|
|
||||||
import spack.binary_distribution as bindist
|
import spack.binary_distribution as bindist
|
||||||
import spack.cmd.buildcache as buildcache
|
import spack.cmd.buildcache as buildcache
|
||||||
|
@ -181,12 +181,12 @@ def test_relocate_links(tmpdir):
|
||||||
relocate_links(["to_self", "to_dependency", "to_system"], prefix_to_prefix)
|
relocate_links(["to_self", "to_dependency", "to_system"], prefix_to_prefix)
|
||||||
|
|
||||||
# These two are relocated
|
# These two are relocated
|
||||||
assert os.readlink("to_self") == str(tmpdir.join("new_prefix_a", "file"))
|
assert readlink("to_self") == str(tmpdir.join("new_prefix_a", "file"))
|
||||||
assert os.readlink("to_dependency") == str(tmpdir.join("new_prefix_b", "file"))
|
assert readlink("to_dependency") == str(tmpdir.join("new_prefix_b", "file"))
|
||||||
|
|
||||||
# These two are not.
|
# These two are not.
|
||||||
assert os.readlink("to_system") == system_path
|
assert readlink("to_system") == system_path
|
||||||
assert os.readlink("to_self_but_relative") == "relative"
|
assert readlink("to_self_but_relative") == "relative"
|
||||||
|
|
||||||
|
|
||||||
def test_needs_relocation():
|
def test_needs_relocation():
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from llnl.util.filesystem import getuid, mkdirp, partition_path, touch, working_dir
|
from llnl.util.filesystem import getuid, mkdirp, partition_path, touch, working_dir
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.paths
|
import spack.paths
|
||||||
|
@ -872,7 +873,7 @@ def _create_files_from_tree(base, tree):
|
||||||
|
|
||||||
def _create_tree_from_dir_recursive(path):
|
def _create_tree_from_dir_recursive(path):
|
||||||
if os.path.islink(path):
|
if os.path.islink(path):
|
||||||
return os.readlink(path)
|
return readlink(path)
|
||||||
elif os.path.isdir(path):
|
elif os.path.isdir(path):
|
||||||
tree = {}
|
tree = {}
|
||||||
for name in os.listdir(path):
|
for name in os.listdir(path):
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
import spack.filesystem_view
|
import spack.filesystem_view
|
||||||
import spack.store
|
import spack.store
|
||||||
|
@ -38,7 +39,7 @@ def create_manifest_entry(path: str) -> Dict[str, Any]:
|
||||||
data: Dict[str, Any] = {"mode": s.st_mode, "owner": s.st_uid, "group": s.st_gid}
|
data: Dict[str, Any] = {"mode": s.st_mode, "owner": s.st_uid, "group": s.st_gid}
|
||||||
|
|
||||||
if stat.S_ISLNK(s.st_mode):
|
if stat.S_ISLNK(s.st_mode):
|
||||||
data["dest"] = os.readlink(path)
|
data["dest"] = readlink(path)
|
||||||
|
|
||||||
elif stat.S_ISREG(s.st_mode):
|
elif stat.S_ISREG(s.st_mode):
|
||||||
data["hash"] = compute_hash(path)
|
data["hash"] = compute_hash(path)
|
||||||
|
@ -90,7 +91,7 @@ def check_entry(path, data):
|
||||||
# instead of `lstat(...).st_mode`. So, ignore mode errors for symlinks.
|
# instead of `lstat(...).st_mode`. So, ignore mode errors for symlinks.
|
||||||
if not stat.S_ISLNK(s.st_mode) and s.st_mode != data["mode"]:
|
if not stat.S_ISLNK(s.st_mode) and s.st_mode != data["mode"]:
|
||||||
res.add_error(path, "mode")
|
res.add_error(path, "mode")
|
||||||
elif stat.S_ISLNK(s.st_mode) and os.readlink(path) != data.get("dest"):
|
elif stat.S_ISLNK(s.st_mode) and readlink(path) != data.get("dest"):
|
||||||
res.add_error(path, "link")
|
res.add_error(path, "link")
|
||||||
elif stat.S_ISREG(s.st_mode):
|
elif stat.S_ISREG(s.st_mode):
|
||||||
# Check file contents against hash and listed as file
|
# Check file contents against hash and listed as file
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
from spack.package import *
|
from spack.package import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +96,7 @@ def install(self, spec, prefix):
|
||||||
# The archive.bin file is quite fussy and doesn't work as a
|
# The archive.bin file is quite fussy and doesn't work as a
|
||||||
# symlink.
|
# symlink.
|
||||||
if os.path.islink(archive):
|
if os.path.islink(archive):
|
||||||
targ = os.readlink(archive)
|
targ = readlink(archive)
|
||||||
os.unlink(archive)
|
os.unlink(archive)
|
||||||
copy(targ, archive)
|
copy(targ, archive)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
from spack.package import *
|
from spack.package import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,7 +107,7 @@ def install(self, spec, prefix):
|
||||||
for name in files:
|
for name in files:
|
||||||
if name.endswith("." + dso_suffix):
|
if name.endswith("." + dso_suffix):
|
||||||
fpath = join(root, name)
|
fpath = join(root, name)
|
||||||
src = os.readlink(fpath)
|
src = readlink(fpath)
|
||||||
install(src, prefix.lib)
|
install(src, prefix.lib)
|
||||||
|
|
||||||
for root, dirs, files in os.walk("."):
|
for root, dirs, files in os.walk("."):
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from llnl.util.symlink import readlink
|
||||||
|
|
||||||
import spack.build_environment
|
import spack.build_environment
|
||||||
from spack.package import *
|
from spack.package import *
|
||||||
from spack.util.executable import Executable
|
from spack.util.executable import Executable
|
||||||
|
@ -79,7 +81,7 @@ def symlink_luajit(self):
|
||||||
assert len(luajits) >= 1
|
assert len(luajits) >= 1
|
||||||
luajit = luajits[0]
|
luajit = luajits[0]
|
||||||
if os.path.islink(luajit):
|
if os.path.islink(luajit):
|
||||||
luajit = os.readlink(luajit)
|
luajit = readlink(luajit)
|
||||||
symlink(luajit, "lua")
|
symlink(luajit, "lua")
|
||||||
|
|
||||||
with working_dir(self.prefix.include):
|
with working_dir(self.prefix.include):
|
||||||
|
|
Loading…
Reference in a new issue