summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/llnl/util/filesystem.py20
-rw-r--r--lib/spack/spack/binary_distribution.py4
-rw-r--r--lib/spack/spack/relocate.py11
-rw-r--r--lib/spack/spack/test/llnl/util/filesystem.py11
-rw-r--r--lib/spack/spack/util/filesystem.py16
5 files changed, 49 insertions, 13 deletions
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
index a876a76c27..4732924572 100644
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -24,6 +24,7 @@ from typing import (
Callable,
Deque,
Dict,
+ Generator,
Iterable,
List,
Match,
@@ -2838,6 +2839,25 @@ def temporary_dir(
remove_directory_contents(tmp_dir)
+@contextmanager
+def edit_in_place_through_temporary_file(file_path: str) -> Generator[str, None, None]:
+ """Context manager for modifying ``file_path`` in place, preserving its inode and hardlinks,
+ for functions or external tools that do not support in-place editing. Notice that this function
+ is unsafe in that it works with paths instead of a file descriptors, but this is by design,
+ since we assume the call site will create a new inode at the same path."""
+ tmp_fd, tmp_path = tempfile.mkstemp(
+ dir=os.path.dirname(file_path), prefix=f"{os.path.basename(file_path)}."
+ )
+ # windows cannot replace a file with open fds, so close since the call site needs to replace.
+ os.close(tmp_fd)
+ try:
+ shutil.copyfile(file_path, tmp_path, follow_symlinks=True)
+ yield tmp_path
+ shutil.copyfile(tmp_path, file_path, follow_symlinks=True)
+ finally:
+ os.unlink(tmp_path)
+
+
def filesummary(path, print_bytes=16) -> Tuple[int, bytes]:
"""Create a small summary of the given file. Does not error
when file does not exist.
diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py
index db26f32c00..da8f7d0364 100644
--- a/lib/spack/spack/binary_distribution.py
+++ b/lib/spack/spack/binary_distribution.py
@@ -2334,7 +2334,9 @@ def relocate_package(spec):
if not codesign:
return
for binary in changed_files:
- codesign("-fs-", binary)
+ # preserve the original inode by running codesign on a copy
+ with fsys.edit_in_place_through_temporary_file(binary) as tmp_binary:
+ codesign("-fs-", tmp_binary)
# If we are installing back to the same location
# relocate the sbang location if the spack directory changed
diff --git a/lib/spack/spack/relocate.py b/lib/spack/spack/relocate.py
index 627c9e2b05..dda17a128e 100644
--- a/lib/spack/spack/relocate.py
+++ b/lib/spack/spack/relocate.py
@@ -13,6 +13,7 @@ from typing import List, Optional
import macholib.mach_o
import macholib.MachO
+import llnl.util.filesystem as fs
import llnl.util.lang
import llnl.util.tty as tty
from llnl.util.lang import memoized
@@ -275,10 +276,10 @@ def modify_macho_object(cur_path, rpaths, deps, idpath, paths_to_paths):
# Deduplicate and flatten
args = list(itertools.chain.from_iterable(llnl.util.lang.dedupe(args)))
+ install_name_tool = executable.Executable("install_name_tool")
if args:
- args.append(str(cur_path))
- install_name_tool = executable.Executable("install_name_tool")
- install_name_tool(*args)
+ with fs.edit_in_place_through_temporary_file(cur_path) as temp_path:
+ install_name_tool(*args, temp_path)
def macholib_get_paths(cur_path):
@@ -717,8 +718,8 @@ def fixup_macos_rpath(root, filename):
# No fixes needed
return False
- args.append(abspath)
- executable.Executable("install_name_tool")(*args)
+ with fs.edit_in_place_through_temporary_file(abspath) as temp_path:
+ executable.Executable("install_name_tool")(*args, temp_path)
return True
diff --git a/lib/spack/spack/test/llnl/util/filesystem.py b/lib/spack/spack/test/llnl/util/filesystem.py
index 1a32e5707c..8b512a2c5f 100644
--- a/lib/spack/spack/test/llnl/util/filesystem.py
+++ b/lib/spack/spack/test/llnl/util/filesystem.py
@@ -1249,3 +1249,14 @@ def test_find_input_types(tmp_path: pathlib.Path):
with pytest.raises(TypeError):
fs.find(1, "file.txt") # type: ignore
+
+
+def test_edit_in_place_through_temporary_file(tmp_path):
+ (tmp_path / "example.txt").write_text("Hello")
+ current_ino = os.stat(tmp_path / "example.txt").st_ino
+ with fs.edit_in_place_through_temporary_file(tmp_path / "example.txt") as temporary:
+ os.unlink(temporary)
+ with open(temporary, "w") as f:
+ f.write("World")
+ assert (tmp_path / "example.txt").read_text() == "World"
+ assert os.stat(tmp_path / "example.txt").st_ino == current_ino
diff --git a/lib/spack/spack/util/filesystem.py b/lib/spack/spack/util/filesystem.py
index b296438fe8..eac5c58918 100644
--- a/lib/spack/spack/util/filesystem.py
+++ b/lib/spack/spack/util/filesystem.py
@@ -13,7 +13,7 @@ import os
import sys
from llnl.util import tty
-from llnl.util.filesystem import join_path
+from llnl.util.filesystem import edit_in_place_through_temporary_file
from llnl.util.lang import memoized
from spack.util.executable import Executable, which
@@ -81,12 +81,11 @@ def fix_darwin_install_name(path):
Parameters:
path (str): directory in which .dylib files are located
"""
- libs = glob.glob(join_path(path, "*.dylib"))
+ libs = glob.glob(os.path.join(path, "*.dylib"))
+ install_name_tool = Executable("install_name_tool")
+ otool = Executable("otool")
for lib in libs:
- # fix install name first:
- install_name_tool = Executable("install_name_tool")
- install_name_tool("-id", lib, lib)
- otool = Executable("otool")
+ args = ["-id", lib]
long_deps = otool("-L", lib, output=str).split("\n")
deps = [dep.partition(" ")[0][1::] for dep in long_deps[2:-1]]
# fix all dependencies:
@@ -98,5 +97,8 @@ def fix_darwin_install_name(path):
# but we don't know builddir (nor how symbolic links look
# in builddir). We thus only compare the basenames.
if os.path.basename(dep) == os.path.basename(loc):
- install_name_tool("-change", dep, loc, lib)
+ args.extend(("-change", dep, loc))
break
+
+ with edit_in_place_through_temporary_file(lib) as tmp:
+ install_name_tool(*args, tmp)