'spack install' can overwrite an existing installation (#5384)
'spack install' can now reinstall a spec even if it has dependents, via the --overwrite option. This option moves the current installation in a temporary directory. If the reinstallation is successful the temporary is removed, otherwise a rollback is performed.
This commit is contained in:

committed by
scheibelp

parent
31813ef2c7
commit
8b7d2d0f24
@@ -24,6 +24,7 @@
|
||||
##############################################################################
|
||||
import collections
|
||||
import errno
|
||||
import hashlib
|
||||
import fileinput
|
||||
import fnmatch
|
||||
import glob
|
||||
@@ -31,12 +32,13 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import six
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from contextlib import contextmanager
|
||||
|
||||
import six
|
||||
from llnl.util import tty
|
||||
from llnl.util.lang import dedupe
|
||||
|
||||
@@ -282,6 +284,60 @@ def working_dir(dirname, **kwargs):
|
||||
os.chdir(orig_dir)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def replace_directory_transaction(directory_name, tmp_root=None):
|
||||
"""Moves a directory to a temporary space. If the operations executed
|
||||
within the context manager don't raise an exception, the directory is
|
||||
deleted. If there is an exception, the move is undone.
|
||||
|
||||
Args:
|
||||
directory_name (path): absolute path of the directory name
|
||||
tmp_root (path): absolute path of the parent directory where to create
|
||||
the temporary
|
||||
|
||||
Returns:
|
||||
temporary directory where ``directory_name`` has been moved
|
||||
"""
|
||||
# Check the input is indeed a directory with absolute path.
|
||||
# Raise before anything is done to avoid moving the wrong directory
|
||||
assert os.path.isdir(directory_name), \
|
||||
'"directory_name" must be a valid directory'
|
||||
assert os.path.isabs(directory_name), \
|
||||
'"directory_name" must contain an absolute path'
|
||||
|
||||
directory_basename = os.path.basename(directory_name)
|
||||
|
||||
if tmp_root is not None:
|
||||
assert os.path.isabs(tmp_root)
|
||||
|
||||
tmp_dir = tempfile.mkdtemp(dir=tmp_root)
|
||||
tty.debug('TEMPORARY DIRECTORY CREATED [{0}]'.format(tmp_dir))
|
||||
|
||||
shutil.move(src=directory_name, dst=tmp_dir)
|
||||
tty.debug('DIRECTORY MOVED [src={0}, dest={1}]'.format(
|
||||
directory_name, tmp_dir
|
||||
))
|
||||
|
||||
try:
|
||||
yield tmp_dir
|
||||
except (Exception, KeyboardInterrupt, SystemExit):
|
||||
# Delete what was there, before copying back the original content
|
||||
if os.path.exists(directory_name):
|
||||
shutil.rmtree(directory_name)
|
||||
shutil.move(
|
||||
src=os.path.join(tmp_dir, directory_basename),
|
||||
dst=os.path.dirname(directory_name)
|
||||
)
|
||||
tty.debug('DIRECTORY RECOVERED [{0}]'.format(directory_name))
|
||||
|
||||
msg = 'the transactional move of "{0}" failed.'
|
||||
raise RuntimeError(msg.format(directory_name))
|
||||
else:
|
||||
# Otherwise delete the temporary directory
|
||||
shutil.rmtree(tmp_dir)
|
||||
tty.debug('TEMPORARY DIRECTORY DELETED [{0}]'.format(tmp_dir))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def hide_files(*file_list):
|
||||
try:
|
||||
@@ -294,6 +350,32 @@ def hide_files(*file_list):
|
||||
shutil.move(bak, f)
|
||||
|
||||
|
||||
def hash_directory(directory):
|
||||
"""Hashes recursively the content of a directory.
|
||||
|
||||
Args:
|
||||
directory (path): path to a directory to be hashed
|
||||
|
||||
Returns:
|
||||
hash of the directory content
|
||||
"""
|
||||
assert os.path.isdir(directory), '"directory" must be a directory!'
|
||||
|
||||
md5_hash = hashlib.md5()
|
||||
|
||||
# Adapted from https://stackoverflow.com/a/3431835/771663
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for name in sorted(files):
|
||||
filename = os.path.join(root, name)
|
||||
# TODO: if caching big files becomes an issue, convert this to
|
||||
# TODO: read in chunks. Currently it's used only for testing
|
||||
# TODO: purposes.
|
||||
with open(filename, 'rb') as f:
|
||||
md5_hash.update(f.read())
|
||||
|
||||
return md5_hash.hexdigest()
|
||||
|
||||
|
||||
def touch(path):
|
||||
"""Creates an empty file at the specified path."""
|
||||
perms = (os.O_WRONLY | os.O_CREAT | os.O_NONBLOCK | os.O_NOCTTY)
|
||||
|
Reference in New Issue
Block a user