ensure the staging dir exists for spack stage -p <PATH>
(#23963)
* ensure that the stage root exists for `spack stage -p <PATH>` * add test to verify `spack stage -p <PATH>` works! * move out shared tmp staging path setup to a fixture to fix the test
This commit is contained in:
parent
281b0e8c92
commit
b369ff461a
@ -3,12 +3,15 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
|
||||||
import spack.environment as ev
|
import spack.environment as ev
|
||||||
import spack.repo
|
import spack.repo
|
||||||
import spack.cmd
|
import spack.cmd
|
||||||
import spack.cmd.common.arguments as arguments
|
import spack.cmd.common.arguments as arguments
|
||||||
|
import spack.stage
|
||||||
|
|
||||||
description = "expand downloaded archive in preparation for install"
|
description = "expand downloaded archive in preparation for install"
|
||||||
section = "build"
|
section = "build"
|
||||||
@ -24,6 +27,12 @@ def setup_parser(subparser):
|
|||||||
|
|
||||||
|
|
||||||
def stage(parser, args):
|
def stage(parser, args):
|
||||||
|
# We temporarily modify the working directory when setting up a stage, so we need to
|
||||||
|
# convert this to an absolute path here in order for it to remain valid later.
|
||||||
|
custom_path = os.path.abspath(args.path) if args.path else None
|
||||||
|
if custom_path:
|
||||||
|
spack.stage.create_stage_root(custom_path)
|
||||||
|
|
||||||
if not args.specs:
|
if not args.specs:
|
||||||
env = ev.get_env(args, 'stage')
|
env = ev.get_env(args, 'stage')
|
||||||
if env:
|
if env:
|
||||||
@ -44,12 +53,12 @@ def stage(parser, args):
|
|||||||
specs = spack.cmd.parse_specs(args.specs, concretize=False)
|
specs = spack.cmd.parse_specs(args.specs, concretize=False)
|
||||||
|
|
||||||
# prevent multiple specs from extracting in the same folder
|
# prevent multiple specs from extracting in the same folder
|
||||||
if len(specs) > 1 and args.path:
|
if len(specs) > 1 and custom_path:
|
||||||
tty.die("`--path` requires a single spec, but multiple were provided")
|
tty.die("`--path` requires a single spec, but multiple were provided")
|
||||||
|
|
||||||
for spec in specs:
|
for spec in specs:
|
||||||
spec = spack.cmd.matching_spec_from_env(spec)
|
spec = spack.cmd.matching_spec_from_env(spec)
|
||||||
package = spack.repo.get(spec)
|
package = spack.repo.get(spec)
|
||||||
if args.path:
|
if custom_path:
|
||||||
package.path = args.path
|
package.path = custom_path
|
||||||
package.do_stage()
|
package.do_stage()
|
||||||
|
@ -44,7 +44,8 @@
|
|||||||
stage_prefix = 'spack-stage-'
|
stage_prefix = 'spack-stage-'
|
||||||
|
|
||||||
|
|
||||||
def _create_stage_root(path):
|
def create_stage_root(path):
|
||||||
|
# type: (str) -> None
|
||||||
"""Create the stage root directory and ensure appropriate access perms."""
|
"""Create the stage root directory and ensure appropriate access perms."""
|
||||||
assert path.startswith(os.path.sep) and len(path.strip()) > 1
|
assert path.startswith(os.path.sep) and len(path.strip()) > 1
|
||||||
|
|
||||||
@ -99,6 +100,15 @@ def _create_stage_root(path):
|
|||||||
tty.warn("Expected user {0} to own {1}, but it is owned by {2}"
|
tty.warn("Expected user {0} to own {1}, but it is owned by {2}"
|
||||||
.format(user_uid, p, p_stat.st_uid))
|
.format(user_uid, p, p_stat.st_uid))
|
||||||
|
|
||||||
|
spack_src_subdir = os.path.join(path, _source_path_subdir)
|
||||||
|
# When staging into a user-specified directory with `spack stage -p <PATH>`, we need
|
||||||
|
# to ensure the `spack-src` subdirectory exists, as we can't rely on it being
|
||||||
|
# created automatically by spack. It's not clear why this is the case for `spack
|
||||||
|
# stage -p`, but since `mkdirp()` is idempotent, this should not change the behavior
|
||||||
|
# for any other code paths.
|
||||||
|
if not os.path.isdir(spack_src_subdir):
|
||||||
|
mkdirp(spack_src_subdir, mode=stat.S_IRWXU)
|
||||||
|
|
||||||
|
|
||||||
def _first_accessible_path(paths):
|
def _first_accessible_path(paths):
|
||||||
"""Find the first path that is accessible, creating it if necessary."""
|
"""Find the first path that is accessible, creating it if necessary."""
|
||||||
@ -110,7 +120,7 @@ def _first_accessible_path(paths):
|
|||||||
return path
|
return path
|
||||||
else:
|
else:
|
||||||
# Now create the stage root with the proper group/perms.
|
# Now create the stage root with the proper group/perms.
|
||||||
_create_stage_root(path)
|
create_stage_root(path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
from spack.main import SpackCommand
|
from spack.main import SpackCommand
|
||||||
import spack.environment as ev
|
import spack.environment as ev
|
||||||
@ -31,27 +32,30 @@ def fake_stage(pkg, mirror_only=False):
|
|||||||
assert len(expected) == 0
|
assert len(expected) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_stage_path(monkeypatch):
|
@pytest.fixture(scope='function')
|
||||||
"""Verify that --path only works with single specs."""
|
def check_stage_path(monkeypatch, tmpdir):
|
||||||
|
expected_path = os.path.join(str(tmpdir), 'x')
|
||||||
|
|
||||||
def fake_stage(pkg, mirror_only=False):
|
def fake_stage(pkg, mirror_only=False):
|
||||||
assert pkg.path == 'x'
|
assert pkg.path == expected_path
|
||||||
|
assert os.path.isdir(expected_path), expected_path
|
||||||
|
|
||||||
monkeypatch.setattr(spack.package.PackageBase, 'do_stage', fake_stage)
|
monkeypatch.setattr(spack.package.PackageBase, 'do_stage', fake_stage)
|
||||||
|
|
||||||
stage('--path=x', 'trivial-install-test-package')
|
return expected_path
|
||||||
|
|
||||||
|
|
||||||
def test_stage_path_errors_multiple_specs(monkeypatch):
|
def test_stage_path(check_stage_path):
|
||||||
"""Verify that --path only works with single specs."""
|
"""Verify that --path only works with single specs."""
|
||||||
|
stage('--path={0}'.format(check_stage_path), 'trivial-install-test-package')
|
||||||
|
|
||||||
def fake_stage(pkg, mirror_only=False):
|
|
||||||
pass
|
|
||||||
|
|
||||||
monkeypatch.setattr(spack.package.PackageBase, 'do_stage', fake_stage)
|
|
||||||
|
|
||||||
|
def test_stage_path_errors_multiple_specs(check_stage_path):
|
||||||
|
"""Verify that --path only works with single specs."""
|
||||||
with pytest.raises(spack.main.SpackCommandError):
|
with pytest.raises(spack.main.SpackCommandError):
|
||||||
stage('--path=x', 'trivial-install-test-package', 'mpileaks')
|
stage('--path={0}'.format(check_stage_path),
|
||||||
|
'trivial-install-test-package',
|
||||||
|
'mpileaks')
|
||||||
|
|
||||||
|
|
||||||
def test_stage_with_env_outside_env(mutable_mock_env_path, monkeypatch):
|
def test_stage_with_env_outside_env(mutable_mock_env_path, monkeypatch):
|
||||||
|
@ -691,14 +691,14 @@ def test_first_accessible_path(self, tmpdir):
|
|||||||
shutil.rmtree(str(name))
|
shutil.rmtree(str(name))
|
||||||
|
|
||||||
def test_create_stage_root(self, tmpdir, no_path_access):
|
def test_create_stage_root(self, tmpdir, no_path_access):
|
||||||
"""Test _create_stage_root permissions."""
|
"""Test create_stage_root permissions."""
|
||||||
test_dir = tmpdir.join('path')
|
test_dir = tmpdir.join('path')
|
||||||
test_path = str(test_dir)
|
test_path = str(test_dir)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if getpass.getuser() in str(test_path).split(os.sep):
|
if getpass.getuser() in str(test_path).split(os.sep):
|
||||||
# Simply ensure directory created if tmpdir includes user
|
# Simply ensure directory created if tmpdir includes user
|
||||||
spack.stage._create_stage_root(test_path)
|
spack.stage.create_stage_root(test_path)
|
||||||
assert os.path.exists(test_path)
|
assert os.path.exists(test_path)
|
||||||
|
|
||||||
p_stat = os.stat(test_path)
|
p_stat = os.stat(test_path)
|
||||||
@ -706,7 +706,7 @@ def test_create_stage_root(self, tmpdir, no_path_access):
|
|||||||
else:
|
else:
|
||||||
# Ensure an OS Error is raised on created, non-user directory
|
# Ensure an OS Error is raised on created, non-user directory
|
||||||
with pytest.raises(OSError) as exc_info:
|
with pytest.raises(OSError) as exc_info:
|
||||||
spack.stage._create_stage_root(test_path)
|
spack.stage.create_stage_root(test_path)
|
||||||
|
|
||||||
assert exc_info.value.errno == errno.EACCES
|
assert exc_info.value.errno == errno.EACCES
|
||||||
finally:
|
finally:
|
||||||
@ -748,10 +748,10 @@ def _stat(path):
|
|||||||
#
|
#
|
||||||
# with monkeypatch.context() as m:
|
# with monkeypatch.context() as m:
|
||||||
# m.setattr(os, 'stat', _stat)
|
# m.setattr(os, 'stat', _stat)
|
||||||
# spack.stage._create_stage_root(user_path)
|
# spack.stage.create_stage_root(user_path)
|
||||||
# assert os.stat(user_path).st_uid != os.getuid()
|
# assert os.stat(user_path).st_uid != os.getuid()
|
||||||
monkeypatch.setattr(os, 'stat', _stat)
|
monkeypatch.setattr(os, 'stat', _stat)
|
||||||
spack.stage._create_stage_root(user_path)
|
spack.stage.create_stage_root(user_path)
|
||||||
|
|
||||||
# The following check depends on the patched os.stat as a poor
|
# The following check depends on the patched os.stat as a poor
|
||||||
# substitute for confirming the generated warnings.
|
# substitute for confirming the generated warnings.
|
||||||
|
Loading…
Reference in New Issue
Block a user